2019年6月15日土曜日

この頃 流行りの 言語たち(他)でベンチマーク (Dart, Go, Julia, Nim, Python, Rust 他)

勉強の為に転載しました。
http://h-miyako.hatenablog.com/entry/2015/01/23/060000

自分が気になっている、主に最近のプログラミング言語ベンチマークをやってみました。方法は、42番めのフィボナッチ数列の値を計算する時間を測るだけです。フィボナッチで各種言語をベンチマーク - satosystemsの日記 を参考にさせていただきました。
  • 注意
    • 筆者はPythonくらいしか使ったことない素人です

言語紹介

測定した言語は、以下の11種類です。
選択基準は、
  • メジャーっぽい
  • 自分が知っていた
  • 自分が気になった
  • 環境構築が楽だった(or すでに構築済みだった)
  • 怖くない
などです。気分と手間で選びました。

測定条件

上記の言語でフィボナッチ数列の42番目の数を求める時間を測りました。42番目なのはなんとなく全部の言語がまともに測定できる範囲に収まったからです*1
あと、今回はコンパイルが必要な言語はコンパイル時間も測りました。 コンパイル時間もコードを書くときには重要な要素だと思うので、参考にしていただければ。

2015年2月22日 10時16分 追記

Cの最適化オプションを-O3にするとNimよりCの方が速いとのことです。この記事では-O2で実験していました。申し訳ありません。-O3オプション付きでコンパイルすると、NimよりもCの方が若干速くなります。

ソースコード

ベースのコードは単純なこれです(python)。
def fib(n):
    if n < 2: return n
    return fib(n - 2) + fib(n - 1)
print(fib(38))
一応できる限り条件を揃えるため、以下のルールに沿って各言語のコードを用意しました。
  • まず n < 2 で条件分岐し、真ならそのまま n を返す
  • 偽ならば(elseは使わず)if節を抜け、関数の最後でfib(n-1)+fib(n-2)を返す
  • nはソースコードに直接書く
    • 実行時に引数として与えたほうが公正な気はしますが、詳しくない言語で実行時にパラメータを受け取る方法を調べるのが面倒だったのでこの方式にします
  • 最後に標準出力に結果を出力する
フィボナッチで各種言語をベンチマーク - satosystemsの日記 でベンチマークに使われている言語は、同じコードを一部変更して使わせていただきました。 問題がありましたらご連絡下さい。

C言語

とりあえず入れないとまずいですよね。
コンパイラgcc)による最適化のあり、無し両方で測定しました。 コンパイル時間もどの程度変わるか、気になるところです。
#include <stdio.h>

int fib(int n) {
    if (n < 2) return n;
    return fib(n - 2) + fib(n - 1);
}

int main(int argc, char *argv[]) {
    printf("%d\n", fib(42));
    return 0;
}
gcc -o c c.c; ./c
gcc -O2 -o c2 c.c; ./c2

Dart

Google製の、いわゆるaltJSの一つ。 そのへんのaltJSと違うのは、JavaScriptの代替となることを狙っている点です。 Dartium上でそのまま実行することもできますし、JavaScriptに変換してから普通のブラウザ上で実行することもできます。
int fib(num n) {
  if (n < 2) return n;
  return fib(n - 2) + fib(n - 1);
}

void main() {
  print(fib(42));
}
今回はDart VMで直接実行する場合と、JavaScriptに変換してからnode.jsで実行する場合の二通りを測定しました。 JavaScriptへの変換時間も、一応コンパイル時間として測りました。
dart dart.dart
dart2js -o dart.js dart.dart
node dart.js

Go 言語

引き続きGoogleの言語です。最近人気ですね。 私も結構好きですが、なんとなく流行に乗り遅れてしまった感じです。
言語の説明を読んで印象に残ったのは、コンパイル時間を短くしたかった、というところです。 実際のところどうなのか、今回はついでにコンパイル時間も測ったので参考にして下さい。
package main

import "fmt"

func fib(n int) int {
    if n < 2 {
        return n
    }
    return fib(n-2) + fib(n-1)
}

func main() {
    fmt.Println(fib(42))
}

JavaScript

これもやっといた方がいいだろう、という理由で入れました。 node.jsで実行してます。
function fib(n) {
  if (n < 2){ return n; }
  return fib(n - 2) + fib(n - 1);
}

console.log(fib(42));

Julia

手軽に書けて行末セミコロンもコンパイルも不要だけど速い、という言語。 メインターゲットは学術系っぽいと勝手に思ってます。 MatlabPython (numpy, scipy, pandas, matplotlib), Rあたりが競合だと思います。
その速さは実際の所どうなのか、気になったのでエントリーです。
function fib(n)
  if n < 2
    return n
  end
  return fib(n - 2) + fib(n - 1)
end

println(fib(42))

Lua/Luajit

ブラジル産まれ?のスクリプト言語。 軽量なので組み込まれて使われることが多いそうです。
一時Vim界隈で話題になっていたのでエントリー。 LuaJitも測りました。
function fib(n)
  if n < 2 then
    return n
  end
  return fib(n - 2) + fib(n - 1)
end

print(fib(42))

Nim (旧称Nimrod)

先日の記事に書いた Nim です。 Python風の静的型付コンパイル言語です。
この記事を書くきっかけのひとつは、Nimでフィボナッチ数列の計算をしてみたら妙に速かったことです。ぜひ他の方にもあの驚きを体験して欲しいです。 ちなみに、もうひとつのきっかけは後述するRustです。
proc fib(n: int): int =
  if n < 2:
    return n
  return fib(n - 2) + fib(n - 1)

echo(fib(42))
Compileオプションの異なる以下二通りで計測しました。
  1. 最適化なし × 実行時チェックあり (debug)
    • nim compile nim.nim
  2. 最適化あり × 実行時チェックなし (release)
    • nim compile -d:release nim.nim
また、Nimはコンパイル時にキャッシュを作りますが、今回はコンパイル時間の計測前に削除しています。なので、通常二回目以降のコンパイルではこのベンチマークよりもコンパイル時間が短くなります。
rm -rf ./nimcash

Python

Pythonです。個人的に好きといいますか、それなりの規模のコードを書いたことがあるのはPythonくらいです。 今回はPython2.7, PyPy, Python3.4, PyPy3 を試しました。
def fib(n):
    if n < 2: return n
    return fib(n - 2) + fib(n - 1)

print(fib(42))

Ruby

日本産。最近はバージョンが上がるごとに速くなっているらしいので、1.9と2.0で測りました。
個人的には、以前RedmineRuby/Rails/Redmineのバージョン間の互換性で泣かされたので好きではありません。
def fib(n)
  if (n < 2) then
    return n
  end
  return fib(n - 2) + fib(n - 1)
end

puts fib(42)

Rust

Mozilla謹製。 先日1.0.0-alpha がリリースされました。 おめでとうございます。
以前、このブログで記事を書いたこともありました。 その時の記事では、最適化無しの実行結果でGoと比較して遅いとか書いてしまいました。 最適化オプションに先日気づいて追記したのですが、アクセス数の雰囲気からけっこう多くの人が追記前に読んでいたようで、Rust関係の皆様には申し訳ない気持ちでいっぱいです。
ということで、Rustさんの汚名を雪ぐために、今回はちゃんと最適化して他の言語と比較します。むしろそれがこの記事を書く理由の半分です。
fn fib(n: isize) -> isize {
    if n < 2 { return n; }
    fib(n - 2) + fib(n - 1)
}

fn main() {
    println!("{}", fib(42));
}
  1. 最適化なし
    • rustc rust.rs
  2. 最適化あり
    • rustc -O rust.rs

Vim Script

この記事はVimで書いています。
元々はVimの設定を記述するための言語だったはずですが、 日本語書籍(Vim script テクニックバイブル)が出版されるなど重要な言語になってきたので今回測定しました。
以下のコードで測定しました。
function! s:fib(n)
  if a:n < 2
    return a:n
  endif
  return s:fib(a:n - 2) + s:fib(a:n - 1)
endfunction

print s:fib(42)
qa!
測定方法はもちろん他と同じようにtimeで実行から終了までです。 上記のコードを vim.vim として保存し、以下のように実行しました。
time vim --noplugin -u ./vim.vim -c "q"
Vimの起動時間も含まれるの?という疑問もあるかもしれませんが、結果を見ればその疑問はなくなると思います。

測定環境

以下の環境で測定しました。
  • OS: Ubuntu 14.04, linux 3.13.0-44-lowlatency (64bit)
  • CPU: Core i7 の最初の頃の4コア8スレッド2.66GHz (i7 920?)
  • Memory: 9GB
測定時はCPUのクロックを勝手に変えられないように一応 cpufreqd で100%固定にしました。
測定に使ったスクリプトこちらです。 コンパイル時間の測定を公平に行うため、コンパイル前に保存されているキャッシュや実行ファイルを削除しています。
測定時は、測定スクリプトを nice -n -15 ./benchmark.sh と実行しました。 念の為、優先度を高めにしてます。
集計は手作業で行いました(一度きりしかやるつもりないので)。

測定結果

ソートなんてしません。アルファベット順です。
LanguageVersionコンパイル時間 (s)実行時間 (s)
cgcc 4.8.20.023.993
c (optimized)gcc 4.8.20.292.062
dart1.8.305.139
dart2js with node.js1.8.3 / v0.11.111.6075.583
go1.4.10.3133.795
javascriptnode v0.11.1105.546
julia0.2.106.479
lua5.2.3067.669
luajit2.0.204.962
nim0.10.20.79525.996
nim (optimized)0.10.21.41.625
pypyPython 2.7.3 (PyPy 2.2.1)020.617
pypy3Python 3.2.5 (PyPy 2.4.0)018.271
python22.7.60120.652
python33.4.00142.071
ruby1.91.9.3p484072.74
ruby2.02.0.0p384058.758
rust1.0.0-alpha0.294.692
rust (optimized)1.0.0-alpha0.3033.602
vim script7.4.58004449.79
実行時間最速は Nim です!!!
普通に書いた C(最適化あり)よりも速いです。
冒頭に追記しましたが、-O3オプション付きでコンパイルしたCの方がNimよりも若干速いです。手元での計測及び結果の更新は少々お待ちください・・・
せっかくなのでグラフにします。 ただ、そのまま描画すると Vim Script 以外見えなくなりそうなので、「1000秒間に何回計算が実行できるか」という数値に換算してグラフにします*2。 したがって、数値が「大きいほど速い」という事になります。 グラフの描画には Highcharts.js を使っています。
Calculations per 1,000 seconds25025048548519519517917926426418018015415415152022023838615615494955558877141417172132132782780.20.2cc (optimized)dartdart2js with node.jsgojavascriptjulialualuajitnim (debug)nim (release)pypypypy3python2python3ruby1.9ruby2.0rustrust (optimized)vim script0100200300400500600700Highcharts.com
こうしてみるとNimが圧倒的ですね。
以下、適当にカテゴリ分けしてコメントします。

コンパイル言語組

コンパイルが必要な言語にとっては、コンパイル時間も重要な比較要素です。 というわけで、コンパイル時間と実行時間をプロットします。 単位は秒です。つまり短いほうが速いです。
Time [sec]4.014.012.352.354.114.113.033.034.984.983.93.93.993.992.062.063.83.826261.631.634.694.693.63.60.020.020.290.290.310.310.80.81.41.40.290.290.30.3Time [sec]Compile time [sec]cc (optimized)gonim (debug)nim (release)rustrust (optimized)02468Highcharts.com
実行時間だけの比較だと以下のような感じです。
[速い] C (-O3(仮)) > Nim (release) > C (-O2) > Rust (optimized) > Go > C > Rust >>> Nim (degub) [遅い]
コンパイル時間も含めると最適化ありのCが速いですね。コンパイル時間だけ比較すると、最適化なしのCが速すぎで、他はNimが遅い以外似たような時間です。
Nimのコンパイル時間がかなり長めに出ていますが、今回はキャッシュを使っていないことに留意してください。キャッシュがあると劇的に速くなります。このキャッシュは変更があっても有効です。例えば、今回のフィボナッチ数列のコードの最後に echo(1) という行を付け加えて再度コンパイルすると、約0.2秒で完了しました。したがって、実用上Nimのコンパイル時間が問題になるケースは少ないと言えます。
nim compile -d:release nim.nim
echo "echo(1) >> nim.nim
time nim compile -d:release nim.nim
# >>> 0.2

JITコンパイル

実行時間(秒)だけプロットします。
Calculation Time [sec]5.145.145.585.585.555.556.486.484.964.9620.6220.6218.2718.27dartdart2js with node.jsjavascriptjulialuajitpypypypy30510152025Highcharts.com
速度としてはLuaJitが最速で、DartJavaScriptDart->JavaScript, Julia*3 までが同じくらい、大きく離れて PyPy 二種類という具合になりました。DartはさすがにJavaScript (Node.js)よりは速いですね。
PyPy以外はGoやRustに近い速度を出しています。今回のコードは単純だったので、ほとんどの計算がJITコンパイル後に行われたためでしょう。もっと複雑なコードではコンパイルする言語との差は大きくなりそうです。
PyPyが遅くてPython推しの自分としては悲しい限りです。PystonNumbaもエントリーさせてなんとか一矢報いたかったのですが、あの二つはインストールのハードルが割と高かったため断念しました。CythonはもうCでいいじゃんって感じなので除外です。
[追記: 2015/04/20] コメント欄にてご指摘いただきました。 今回、Julia は Ubuntu の標準リポジトリのものを使ったので、古めのバージョン(v0.2.1, 2014/2/11 リリース)だったようです。 v0.3 で起動速度などが改善されているそうなので、気になる方は試してみてはいかがでしょうか。 [追加終わり]

残り(スクリプト言語組)

最後です。実行時間(秒)をプロットします。見やすくするため、桁がずれているVim scriptさんには枠外に飛び出てもらっています。
Calculation Time [sec]686812112114214273735959luapython2python3ruby1.9ruby2.0vim script050100150200Highcharts.com
一番速いのはRuby 2.0です。Luaが一番速いと予想していたので意外でした。 Ruby 1.9YARVに移行して大分速くなったそうですが、その後も高速化は続いているようです。2.1や2.2も試せれば良かったのですが、私はそんなにRubyが好きではないので諦めました。
Pythonは、Rubyとは逆に、2から3への移行で遅くなってしまっています。 推測ですが、これはPython3で整数型をPython2でのlong相当*4に一本化した影響ではないかと思います。 文字列操作ならもっと3系が速いのでは?と思われる方もいるかもしれませんが、Pythonの文字列型は2系では内部的にasciiだったのが3系ではUnicodeに統一され、メモリ消費が多くなっているので、やっぱり遅くなっているはずです。Pythonで速度が必要な処理を書く時はC APIを使いましょう。
Vim Scriptも有限時間内に完走出来てよかったと思いました(小並感)。

最後に

以上、なんとなくやってみたベンチマークでした。 結果を一言で表すと、
Nim速い!!RustもGoよりちょっと速い!!
でしょうか。なので皆さん、Nimを試しましょう。
単純な比較なので、各言語のごく一部しか比較できていませんが、ある程度特徴は出たのではないかと思っています。 また、一見公平に比較しているように見えて(特にコメントに)筆者の恣意的な何かが溢れています。鵜呑みにしないようご注意ください。 真面目に比較したい方はWebフレームワークのベンチマークなども参照するといいかもしれません。

f:id:h-miyako:20150122225410p:plain
*1:他にも何通りか試しました。
*2:逆数とって1000倍しただけです。値は四捨五入しました。
*3:今回の測定ではインタープリタの起動時間も含まれるため、起動時間の遅い(約2秒)のJuliaには不利な条件でした。起動時間を含めず、単純に計算時間だけならばおそらくJuliaが僅差でLuaJitを上回って最速です。個人的には別にJuliaに流行って欲しくないので脚注でのコメントにとどめます。
*4:Python 2系では整数型にintとlongの二種類がありましたが、Python 3.0でこれはlong型に統一されました。

0 コメント:

コメントを投稿