この頃 流行りの 言語たち(他)でベンチマーク (Dart, Go, Julia, Nim, Python, Rust 他)
自分が気になっている、主に最近のプログラミング言語でベンチマークをやってみました。方法は、42番めのフィボナッチ数列の値を計算する時間を測るだけです。フィボナッチで各種言語をベンチマーク - satosystemsの日記 を参考にさせていただきました。
- 注意
- 筆者はPythonくらいしか使ったことない素人です
言語紹介
測定した言語は、以下の11種類です。
選択基準は、
- メジャーっぽい
- 自分が知っていた
- 自分が気になった
- 環境構築が楽だった(or すでに構築済みだった)
- 怖くない
などです。気分と手間で選びました。
測定条件
上記の言語でフィボナッチ数列の42番目の数を求める時間を測りました。42番目なのはなんとなく全部の言語がまともに測定できる範囲に収まったからです*1。
あと、今回はコンパイルが必要な言語はコンパイル時間も測りました。 コンパイル時間もコードを書くときには重要な要素だと思うので、参考にしていただければ。
2015年2月22日 10時16分 追記
Cの最適化オプションを-O3
にするとNimよりCの方が速いとのことです。この記事では-O2
で実験していました。申し訳ありません。-O3
オプション付きでコンパイルすると、NimよりもCの方が若干速くなります。
Nim vs C 追実験した.10回実行して平均と分散を計算.C言語が早い(差は小さいが統計的に有意).
Nim -d:release 平均4.2199秒 分散0.0018
C言語 -O3 平均 4.0689秒 分散0.0008
— ᴛ. ᴍᴀᴇʜᴀʀᴀ (@tmaehara) February 22, 2015
ソースコード
ベースのコードは単純なこれです(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
手軽に書けて行末セミコロンもコンパイルも不要だけど速い、という言語。 メインターゲットは学術系っぽいと勝手に思ってます。 Matlab, Python (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オプションの異なる以下二通りで計測しました。
- 最適化なし × 実行時チェックあり (
debug
)nim compile nim.nim
- 最適化あり × 実行時チェックなし (
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で測りました。
個人的には、以前RedmineにRuby/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)); }
- 最適化なし
rustc rust.rs
- 最適化あり
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
と実行しました。
念の為、優先度を高めにしてます。
集計は手作業で行いました(一度きりしかやるつもりないので)。
測定結果
ソートなんてしません。アルファベット順です。
Language | Version | コンパイル時間 (s) | 実行時間 (s) |
---|---|---|---|
c | gcc 4.8.2 | 0.02 | 3.993 |
c (optimized) | gcc 4.8.2 | 0.29 | 2.062 |
dart | 1.8.3 | 0 | 5.139 |
dart2js with node.js | 1.8.3 / v0.11.11 | 1.607 | 5.583 |
go | 1.4.1 | 0.313 | 3.795 |
javascript | node v0.11.11 | 0 | 5.546 |
julia | 0.2.1 | 0 | 6.479 |
lua | 5.2.3 | 0 | 67.669 |
luajit | 2.0.2 | 0 | 4.962 |
nim | 0.10.2 | 0.795 | 25.996 |
nim (optimized) | 0.10.2 | 1.4 | 1.625 |
pypy | Python 2.7.3 (PyPy 2.2.1) | 0 | 20.617 |
pypy3 | Python 3.2.5 (PyPy 2.4.0) | 0 | 18.271 |
python2 | 2.7.6 | 0 | 120.652 |
python3 | 3.4.0 | 0 | 142.071 |
ruby1.9 | 1.9.3p484 | 0 | 72.74 |
ruby2.0 | 2.0.0p384 | 0 | 58.758 |
rust | 1.0.0-alpha | 0.29 | 4.692 |
rust (optimized) | 1.0.0-alpha | 0.303 | 3.602 |
vim script | 7.4.580 | 0 | 4449.79 |
普通に書いた C(最適化あり)よりも速いです。
冒頭に追記しましたが、-O3
オプション付きでコンパイルしたCの方がNimよりも若干速いです。手元での計測及び結果の更新は少々お待ちください・・・
せっかくなのでグラフにします。 ただ、そのまま描画すると Vim Script 以外見えなくなりそうなので、「1000秒間に何回計算が実行できるか」という数値に換算してグラフにします*2。 したがって、数値が「大きいほど速い」という事になります。 グラフの描画には Highcharts.js を使っています。
こうしてみるとNimが圧倒的ですね。
以下、適当にカテゴリ分けしてコメントします。
コンパイル言語組
コンパイルが必要な言語にとっては、コンパイル時間も重要な比較要素です。 というわけで、コンパイル時間と実行時間をプロットします。 単位は秒です。つまり短いほうが速いです。
実行時間だけの比較だと以下のような感じです。
[速い] 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コンパイル組
実行時間(秒)だけプロットします。
速度としてはLuaJitが最速で、Dart, JavaScript, Dart->JavaScript, Julia*3 までが同じくらい、大きく離れて PyPy 二種類という具合になりました。DartはさすがにJavaScript (Node.js)よりは速いですね。
PyPy以外はGoやRustに近い速度を出しています。今回のコードは単純だったので、ほとんどの計算がJITコンパイル後に行われたためでしょう。もっと複雑なコードではコンパイルする言語との差は大きくなりそうです。
PyPyが遅くてPython推しの自分としては悲しい限りです。PystonやNumbaもエントリーさせてなんとか一矢報いたかったのですが、あの二つはインストールのハードルが割と高かったため断念しました。CythonはもうCでいいじゃんって感じなので除外です。
[追記: 2015/04/20] コメント欄にてご指摘いただきました。 今回、Julia は Ubuntu の標準リポジトリのものを使ったので、古めのバージョン(v0.2.1, 2014/2/11 リリース)だったようです。 v0.3 で起動速度などが改善されているそうなので、気になる方は試してみてはいかがでしょうか。 [追加終わり]
残り(スクリプト言語組)
最後です。実行時間(秒)をプロットします。見やすくするため、桁がずれているVim scriptさんには枠外に飛び出てもらっています。
一番速いのはRuby 2.0です。Luaが一番速いと予想していたので意外でした。 Ruby 1.9でYARVに移行して大分速くなったそうですが、その後も高速化は続いているようです。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を試しましょう。
単純な比較なので、各言語のごく一部しか比較できていませんが、ある程度特徴は出たのではないかと思っています。 また、一見公平に比較しているように見えて(特にコメントに)筆者の恣意的な何かが溢れています。鵜呑みにしないようご注意ください。 真面目に比較したい方はWebフレームワークのベンチマークなども参照するといいかもしれません。