2015/11/05

20の言語/環境でてきとうにベンチマークしてみた (Rust, Go, Crystal, Nim, Swiftなど)

自分が普段利用している言語や、気になっている言語などを集めてベンチマークを行いました。

次の2つのブログ記事の伝統に則り、再帰のフィボナッチ関数を使って、処理時間や利用メモリなどを計測してみました。

ただし、値は埋め込みではなくて、コマンドライン引数として取れるように変更しています (定数で最適化されると嫌なので)。

言語紹介

測定したのは、次の20言語です。

  • C (Clang)
  • C++ (Clang)
  • Chapel
  • Crystal
  • D (DMD, LDC)
  • Elixir
  • Felix
  • Go
  • Julia
  • Lua (LuaJIT)
  • Nim
  • JavaScript (Node)
  • OCaml
  • PHP
  • Perl
  • Pony
  • Python (Python, Nuitka, PyPy)
  • Ruby
  • Rust
  • Swift

Javaを入れるのが嫌なので、Java VM系の言語は一切ありません。

ソースなどは次のリポジトリにあります。

というわけで、各言語 (環境) を紹介。TIOBE Index for October 2015のランク上位の言語から順に紹介していきます。

C

#include <stdio.h>
#include <stdlib.h>

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

int main(int argc, char** argv) {
  printf("%d\n", fib(atoi(argv[1])));
  return 0;
}

他の言語もそうですが、コンパイルエラーにならない限りはエラー処理は極力無視するスタイルです。

コンパイルはclangなので-Ofast

clang -Ofast -o fib-c fib.c

C++

19個だときりが悪いということで最後に急遽追加。

#include <iostream>
#include <string>

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

int main(int argc, char** argv) {
  std::cout << fib(std::stoi(argv[1])) << std::endl;
  return 0;
}

C++11からstoiが使えるのでちょっと楽になりました。

コンパイルはCのときと同じ-Ofast

clang++ -Ofast -o fib-c++ fib.cc

Python

スクリプト言語としては、一番よく利用して一番好きな言語です。 3項演算子の書きづらさだけはどうにかして欲しいですけれど。

Macに初期状態で入っているpythonをそのまま利用しています。

import sys

def fib(n):
    return n if n < 2 else fib(n - 1) + fib(n - 2)

print(fib(int(sys.argv[1])))

実行時のオプション指定はなし。

python ./fib.py

PyPy

PythonのJIT実行環境。最近4.0.0が出ました。

ソースはPythonのをそのまま利用。

実行時オプションがいろいろ曲者で、設定次第で速度が倍以上違うので困りました。そこそこ速そうな--jit function_threshold=5000を利用しています。

$ time pypy ./fib.py 42
267914296
pypy ./fib.py 42  10.06s user 0.09s system 99% cpu 10.181 total

$ time pypy --jit function_threshold=5000 ./fib.py 42
267914296
pypy --jit function_threshold=5000 ./fib.py 42  3.79s user 0.07s system 99% cpu 3.878 total

Nuitka

ネイティブバイナリを作ることができるPythonコンパイラ、ということでPythonと比べてどれくらい早くなるかを試してみました。 ソースはPythonのものをそのまま利用しています。

コンパイルは--clangを付けましたが多分省略しても同じ。

nuitka --clang fib.py

PHP

ハイパーテキスト向けのプリプロセッサ。 もう少しでPHP 7もリリースされるんですかね。 Macに初期状態で入っていたので、書きました。

<?php
function fib($n) {
    return $n < 2 ? $n : fib($n - 1) + fib($n - 2);
}

echo fib($argv[1]), "\n";
?>

実行時のオプション指定はなし。

php  ./fib.php

Node

Chrome V8を搭載したJavaScript実行環境。

function fib(n) {
    return n < 2 ? n : fib(n - 1) + fib(n - 2);
}

console.log(fib(process.argv[2]));

V8のオプションがいろいろあって死にそうになりましたが、ちょっといじってみても変わりがなかったので、オプションなしでデフォルトのまま実行しています。

node ./fib.js

Perl

言わずと知れたスクリプト言語。蝶のロゴが可愛いPerl 6はPerl 5の妹らしい。 Macに初期状態で入っているperlをそのまま利用しています。

sub fib {
    my $n = shift;
    return $n < 2 ? $n : fib($n - 1) + fib($n - 2);
}

print fib($ARGV[0]), "\n";

実行時のオプション指定はなし。

perl ./fib.pl

ちなみに、Perl 6も試したのですが、Perl 5以上に猛烈に遅いので今回はベンチマーク対象にしていません (ソースはfib.pl6)。

$ time perl6 --optimize=3 fib.pl6 30
832040
perl6 --optimize=3 fib.pl6 30  13.19s user 0.32s system 99% cpu 13.539 total

$ time perl fib.pl 30
832040
perl fib.pl 30  0.83s user 0.01s system 99% cpu 0.845 total

Ruby

スクリプト言語。 Macに初期状態で入っているrubyをそのまま利用しています。

def fib(n)
  if n < 2
    n
  else
    fib(n - 1) + fib(n - 2)
  end
end

puts fib(ARGV[0].to_i())

実行時のオプション指定はなし。

ruby ./fib.rb

Crystal

Ruby作者のMatz氏も驚くRuby風のコンパイル言語。LLVM BC経由でネイティブバイナリを吐いてくれます。

ソースはRubyのものをそのまま利用しています。

ちなみに、3項演算子あたりはRubyと違うようなので、両方で実行できるようなソースにしています。

コンパイル時オプションには--release。さらに、リンクエラーになったので-L/usr/local/libを追加しています。

crystal build --link-flags -L/usr/local/lib --release -o fib-crystal fib.rb

Swift

年末にはオープンソースになる予定のApple製の言語。 TIOBEでのランクもじわじわと上がっています。というかObjective-Cがランク下げすぎです。

func fib(n: Int) -> Int {
    return n < 2 ? n : fib(n - 1)  + fib(n - 2)
}

print(fib(Int(Process.arguments[1])!))

コンパイルオプションは-O

swiftc -O -o fib-swift fib.swift

D

Facebookが手を入れ始めた言語。

import std.stdio;
import std.conv;

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

void main(string[] args) {
    writeln(fib(to!int(args[1])));
}

コンパイラはDMDとLDCの2種類あり、コンパイルオプションはそれぞれ次の通り。-O5とか実装があるのを始めて見ました。

dmd -m64 -release -O -offib-d fib.d
ldc2 -L=-w -O5 -m64 -offib-d-ldc fib.d

LuaJIT

軽量スクリプト言語として有名なLuaのJITコンパイラです。

function fib(n)
  if (n < 2) then
    return n
  else
    return fib(n - 1) + fib(n - 2)
  end
end

print(fib(tonumber(arg[1])))

実行時オプションは-O3

luajit -O3 ./fib.lua

Elixir

Erlang VM上で動作する関数型言語。 今回始めて書きましたが、モジュール宣言は必須なのかがいまいちわからず。

defmodule Fibonacci do
  def fib(n) when n < 2 do
      n
  end

  def fib(n) do
      fib(n - 1) + fib(n - 2)
  end
end

if System.argv != [] do
  IO.puts Fibonacci.fib(String.to_integer(List.first(System.argv())))
end

実行時オプションは特になし。

elixir ./fib.exs

OCaml

下で紹介しているFelixをコンパイルするための副産物として入ったので、ついでにコードも書いてみました。

今回始めて書いたので文法がわからずに苦労しました。

let rec fib n =
  if n < 2 then n else fib (n - 1) + fib (n - 2);;

print_int (fib (int_of_string Sys.argv.(1)));
print_newline ()

最適化コンパイラのocamloptを使うのでコンパイラオプションは特になし。

ocamlopt -o fib-ocaml fib.ml

Rust

Mozilla Research製の言語。if let SomeのあたりはSwiftっぽいですね。

use std::env;

fn fib(n: isize) -> isize {
    if n < 2 {
        n
    } else {
        fib(n - 1)  + fib(n - 2)
    }
}

fn main() {
    if let Some(s) = env::args().nth(1) {
        if let Ok(n) = s.parse::<isize>() {
            println!("{}", fib(n));
        }
    }
}

コンパイルオプションは-Oのみ。

rustc -O -o fib-rust fib.rs

Go

TIOBEでのランクだと50位より下だったことにちょっと驚いてしまったGoogle製の言語。 マスコットキャラのGopherが可愛い。

package main

import (
  "fmt"
  "os"
  "strconv"
)

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

func main() {
  n, _ := strconv.Atoi(os.Args[1])
  fmt.Println(fib(n))
}

コンパイルオプションはないっぽい。buildにすると最適化されているのだろうか…。

go build -o fib-go fib.go

Julia

MathematicaやMATLABの流れを汲む科学計算向けの言語、らしい。

fib(n) = n < 2 ? n : fib(n - 1) + fib(n - 2)

println(fib(parse(ARGS[1])))

実行時には最適化オプションの-Oを追加。

julia -O ./fib.jl

Nim

マルチパラダイム言語。昔はPascalで書かれていたらしい。確かにresultに代入できたりするところがPascalっぽい。 デフォルトではCソースを吐いてからそれをコンパイルしています。

import os
import strutils

proc fib(n: int): int =
  result = if n < 2: n else: fib(n - 1) + fib(n - 2)

echo(fib(parseInt(paramStr(1))))

コンパイルオプションはいろいろ付けていますが、-d:releaseが重要。

nim c --verbosity:0 -d:release --app:console --opt:speed --out:fib-nim fib.nim

Chapel

ここからマイナー言語の紹介。といっても、これはGitHubのTrending repositoriesに名前があるだけ有名なほうです。

スーパーコンピュータで有名なクレイがつくった並列計算のための言語 (ソースはGitHubで公開)。 2004年くらいから開発が始まっているみたいですが、息が長い割にまったく名前を聞いたことがなかったです。

proc fib(n): int return if n < 2 then n else fib(n - 1) + fib(n - 2);

config var n = 1;
writeln(fib(n));

おもしろいことに、グローバル変数の宣言にconfigと付けると、コマンドライン引数と見なしてくれる機能があること (上の例だと、--n=4みたいに指定できるようになる)。 まさに、コマンドラインツールを書くための言語。

コンパイル引数はよくありがちな-O

chpl -O -o fib-chapel fib.chpl

Pony

Erlangのようにアクターモデルを持ちつつ、LLVMベースでネイティブバイナリにコンパイルできるため、実行パフォーマンスがよい言語。

actor Main
  fun fib(n: U32): U32 =>
    if n < 2 then
      return n
    else
      return fib(n - 1) + fib(n - 2)
    end

  new create(env: Env) =>
    let n: U32 = try env.args(1).u32() else 0 end
    env.out.print(fib(n).string())

コンパイル引数はちょっと特殊でponycのみでよく、カレントディレクトリのソースを捜索してコンパイルするみたいです。

ponyc

Felix

恐らく知っている人はいないであろう超マイナー言語。言語のロゴはこれでいいのか…? 5年ぶりくらいにメジャーアップデートが出そうな気配です。言語的にはC++のトランスレータ的な趣きが強い。 C++との連携にグルーコードが不要とか、Cよりも早いバイナリを作るよとか書かれていて気になったので試してみました。

最近、WindowsとLinuxにはバイナリが用意されるようになったみたいですが、 Macではバイナリがない上に2015.10.30-rc8だとコンパイルエラーになるので、どうにかコンパイルできたコミット2b4c4a7のバイナリを利用しています。

fun fib (n:int) =>
    if n < 2 then n else fib(n - 1) + fib(n - 2) endif;

println $ str $ fib $ int $ System::argv 1;

コンパイルオプションは次の通り。

flx --static -c -O3 -o fib-felix fib.flx

速度

上で紹介したベンチマーク記事42を使っていたので、各言語でfib(42)した結果をグラフにまとめました。

コンパイル言語のみでまとめなおしたものがこちら。最速はOCamlで、続いてSwift, Rust, D (LDC), Felix, Crystal, Cとなりました。 OCamlとSwiftが一歩だけ抜けて早いかんじです。使っている分にはSwiftが早い感じはあまりしませんけれど。

fib(1)のときはあまり処理がないので、だいたい起動処理時間と思ってよいはず…ということでスクリプト言語の起動時間としてまとめたものがこちら。コンパイル言語はすべて起動時間0なので省略しています。 Perlの圧倒的な起動時間の速さに驚かされます。

で、フィボナッチの値を1から45 (スクリプト言語は42) まで変化させてみたときの結果は次の通り。 全てを覆い、天を突き抜けようとしているのがPerlです。 PyPyは処理が多くなると最適化をかけ直したりするのか、処理時間が安定しません。

上のグラフを、右下のみを拡大させた結果は次のようになります。

全体を対数グラフにしたものがこちら。Juliaの「ウサギとカメ」のカメ感がなかなかよいです。

右下のみを拡大して見やすくしたものがこちら。 こうして見ると、OCamlとSwiftの2つはグラフが被っていて、だいたい同じ結果になっていることがわかります。

実行時メモリ使用量

それぞれ、fib(35)の値を取るときに/usr/bin/time -lして、そのmaximum resident set sizeの値を結果としました。Juliaがメモリ食いすぎでした。

言語/環境 サイズ (バイト)
C 688,128
C++ 692,224
Felix 794,624
Nim 819,200
OCaml 864,256
Pony 995,328
LuaJIT 1,134,592
Rust 1,134,592
D (DMD) 1,142,784
Crystal 1,236,992
D (LDC) 1,327,104
Go 1,462,272
Perl 1,683,456
Chapel 1,839,104
Swift 3,629,056
Python 4,759,552
Nuitka 4,849,664
Ruby 7,135,232
PHP 9,342,976
Node 18,325,504
Elixir 33,153,024
PyPy 51,376,128
Julia 97,636,352

コンパイル時間

コンパイラ言語のみ、コンパイル時間を計測してみました。 直前にmake && make cleanしてからの結果。

言語/環境 時間 (秒)
C 0.06
OCaml 0.12
Felix 0.23
D (dmd) 0.25
Rust 0.26
Swift 0.26
Go 0.37
C++ 0.42
D (ldc) 0.64
Nim 0.68
Nuitka 1.57
Crystal 2.06
Pony 2.42
Chapel 3.26

バイナリ

コンパイラ言語のバイナリの状況です。

まずはファイルサイズ。Cは群を抜いて小さいですね。

言語/環境 サイズ (バイト)
C 8,504
C++ 10,180
Swift 17,560
Nim 42,896
Crystal 65,580
Pony 82,432
Nuitka 88,200
OCaml 187,596
Rust 277,604
Felix 497,088
Chapel 520,132
D (dmd) 594,608
D (ldc) 2,160,220
Go 2,324,112

続いて、依存ライブラリ。 Goは何にも依存していません。これならバイナリサイズが大きいのも納得です。 libSystem.B.dylibは他の環境で言うところのlibcに当たるものらしいので、C言語やC言語に変換系の言語ではこれに依存しています。

fib-go:
fib-c:
    /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1225.1.1)
fib-d:
    /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1225.1.1)
fib-d-ldc:
    /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1225.1.1)
fib-nim:
    /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1225.1.1)
fib-rust:
    /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1225.1.1)
fib-ocaml:
    /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1225.1.1)
fib-pony:
    /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1225.1.1)
fib-c++:
    /usr/lib/libc++.1.dylib (compatibility version 1.0.0, current version 120.1.0)
    /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1225.1.1)
fib-felix:
    /usr/lib/libc++.1.dylib (compatibility version 1.0.0, current version 120.1.0)
    /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1225.1.1)
fib-swift:
    /usr/lib/libobjc.A.dylib (compatibility version 1.0.0, current version 228.0.0)
    /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1225.1.1)
    @rpath/libswiftCore.dylib (compatibility version 0.0.0, current version 0.0.0)
fib-nuitka:
    /usr/lib/libc++.1.dylib (compatibility version 1.0.0, current version 120.1.0)
    /System/Library/Frameworks/Python.framework/Versions/2.7/Python (compatibility version 2.7.0, current version 2.7.10)
    /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1225.1.1)
fib-chapel:
    /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1225.1.1)
    /usr/local/opt/chapel/libexec/third-party/gmp/install/darwin-clang-native/lib/libgmp.10.dylib (compatibility version 13.0.0, current version 13.0.0)
    /usr/lib/libc++.1.dylib (compatibility version 1.0.0, current version 120.1.0)
fib-crystal:
    /usr/local/opt/libevent/lib/libevent-2.0.5.dylib (compatibility version 7.0.0, current version 7.9.0)
    /usr/local/opt/libpcl/lib/libpcl.1.dylib (compatibility version 2.0.0, current version 2.11.0)
    /usr/lib/libpcre.0.dylib (compatibility version 1.0.0, current version 1.1.0)
    /usr/local/opt/bdw-gc/lib/libgc.1.dylib (compatibility version 2.0.0, current version 2.3.0)
    /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1225.1.1)

どれも問題なく64-bitバイナリでした。

fib-c:       Mach-O 64-bit executable x86_64
fib-c++:     Mach-O 64-bit executable x86_64
fib-chapel:  Mach-O 64-bit executable x86_64
fib-d:       Mach-O 64-bit executable x86_64
fib-d-ldc:   Mach-O 64-bit executable x86_64
fib-crystal: Mach-O 64-bit executable x86_64
fib-go:      Mach-O 64-bit executable x86_64
fib-felix:   Mach-O 64-bit executable x86_64
fib-nim:     Mach-O 64-bit executable x86_64
fib-rust:    Mach-O 64-bit executable x86_64
fib-swift:   Mach-O 64-bit executable x86_64
fib-ocaml:   Mach-O 64-bit executable x86_64
fib-nuitka:  Mach-O 64-bit executable x86_64
fib-pony:    Mach-O 64-bit executable x86_64

雑感

  • 最速はOCamlとSwift
  • Goのシングルバイナリ感がすごい
  • Perlは実行速度は遅いが、起動の軽さとメモリ利用量の少なさがよい
  • PythonはRubyより遅いのね…
  • Rustは1.4.0になって、Cと同等の速度になった (1.3.0はCの4割くらい遅かった)
  • CrystalはだいたいC同じくらいの速度。ただしコンパイルは遅い
  • Juliaは起動が遅いが、それ以降はコンパイラ言語並みに早い
  • PyPyの速度の不安定さ。ちゃんと使うならチューニング必須
  • Luaのいろいろな軽さ

バージョン

基本的に、brew, brew cask, pipを利用しております。

  • Rust: 最新版パッケージのなかったので公式サイトよりパッケージインストール
  • Nim: 最新版パッケージのなかったので公式サイトよりビルド
  • Felix: パッケージのないのでソースよりビルド (コミット2b4c4a7)
言語/環境 バージョン
C, C++ (Clang) Apple LLVM version 7.0.0 (clang–700.1.76)
Chapel chpl Version 1.12.0
Crystal Crystal 0.9.1 (Fri Oct 30 13:49:50 UTC 2015)
D (dmd) DMD64 D Compiler v2.068
D (ldc) LDC - the LLVM D compiler (0.16.0):
Elixir Elixir 1.1.1
Felix version 15.08.15
Go go version go1.5.1 darwin/amd64
Julia julia version 0.4.0
LuaJIT LuaJIT 2.0.4 – Copyright (C) 2005–2015 Mike Pall. http://luajit.org/
Nim Nim Compiler Version 0.12.0 (2015–10–27) [MacOSX: amd64]
Node v5.0.0
Nuitka 0.5.15
OCaml The OCaml toplevel, version 4.02.3
PHP PHP 5.5.27 (cli) (built: Aug 22 2015 18:31:33)
Perl This is perl 5, version 18, subversion 2 (v5.18.2) built for darwin-thread-multi–2level
Pony 0.2.1
Python Python 2.7.10
PyPy [PyPy 4.0.0 with GCC 4.2.1 Compatible Apple LLVM 7.0.0 (clang–700.1.76)]
Ruby ruby 2.0.0p645 (2015–04–13 revision 50299) [universal.x86_64-darwin15]
Rust rustc 1.4.0 (8ab8581f6 2015–10–27)
Swift Apple Swift version 2.1 (swiftlang–700.1.101.6 clang–700.1.76)

測定環境

  • MacBook Pro (Retina, Mid 2012)
  • Processor 2.6 GHz Intel Core i7
  • Memory 16 GB 1600 MHz DDR3
  • OS OS X El Capitan (15A279b)

関連項目

0 件のコメント:

コメントを投稿

注: コメントを投稿できるのは、このブログのメンバーだけです。