Juliaの実行速度がどのくらい速いのか、簡単なループで計測してみた。やっていることはランダムに作った倍精度浮動小数点数100万個を加算するだけだ。
Juliaのコード
Juliaのコードはこんなかんじ。
randoms = rand(1000000)
function mysum(v)
sum = 0
for i in v
sum += i
end
end
using BenchmarkTools
mysum(randoms)
@benchmark mysum($randoms)
benchmarkを取る前に一度空振りしているのはJITコンパイルさせるため。
Pythonのコード
Pythonのほうも一応速度を意識してインデックスで回してみた。イテレータでやるよりは速いはず。
import random
import time
SIZE = 1000000
randoms = [random.random() for _ in range(SIZE)]
def mysum(v):
sum = 0.0
for i in range(len(v)):
sum += v[i]
return sum
now = time.time()
mysum(randoms)
then = time.time()
print (then - now)
Cのコード
Cはこんな感じ。Cで時間を測定する正しい方法がよくわからないけど、もう少しまともなライブラリはないのだろうか。2019年だぞ。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/time.h>
#define VECSIZE 1000000
int main(){
double randoms[VECSIZE];
for (long i = 0; i < VECSIZE; i++)
randoms[i] = (double)rand() / RAND_MAX;
struct timespec now, then;
clock_gettime(CLOCK_MONOTONIC, &now);
double sum = 0.0;
for (long i = 0; i < VECSIZE; i++)
sum += randoms[i];
clock_gettime(CLOCK_MONOTONIC, &then);
double diff = (double)(then.tv_sec - now.tv_sec) +
(double)(then.tv_nsec - now.tv_nsec) * 0.000000001;
printf("%lf: %lf sec.\n", sum, diff);
}
結果
JuliaはBenchmarkツールを使ってちゃんと計測しているが、PythonやCは一回だけなのでちょっと不公平だけど。実行は手元のMacbook Pro。
Julia
julia> include("test.jl")
500387.1306197926
BenchmarkTools.Trial:
memory estimate: 0 bytes
allocs estimate: 0
--------------
minimum time: 985.463 μs (0.00% GC)
median time: 1.005 ms (0.00% GC)
mean time: 1.024 ms (0.00% GC)
maximum time: 1.728 ms (0.00% GC)
--------------
samples: 4873
evals/sample: 1
まあ、だいたい1msだと言っていいだろう。
Python
$ python test.py
0.0528540611267
だいたい50msぐらい。
C
$ gcc -O3 test.c; ./a.out; ./a.out
500030.059810: 0.001236 sec.
500030.059810: 0.001252 sec.
1.2ms ぐらい。
まとめ
なんと、この単純なループでは、
JuliaとCがほぼ同じ速度、Pythonは50倍遅いという結果になった。型情報も何もつけていないコードでこの速さというのはさすがにびっくり。予想外。ちなみに、いろいろ型情報をつけたコードで試してみたが、速度は変わらなかった。
Cのアセンブラコード出力を見てみるとループアンローリングもされたそこそこ最適化されたコードになっている。Juliaのコードを@code_native
で見てみると、アンロールされていない比較的単純なコードになっている。この後ろにLLVMが来て、更に最適化されるのだろうか?
なお、おそろしいことに、Juliaの組み込み関数sum
を使うとさらに3−4倍速い。
julia> @benchmark sum($randoms)
BenchmarkTools.Trial:
memory estimate: 0 bytes
allocs estimate: 0
--------------
minimum time: 236.386 μs (0.00% GC)
median time: 267.007 μs (0.00% GC)
mean time: 272.185 μs (0.00% GC)
maximum time: 916.661 μs (0.00% GC)
--------------
samples: 10000
evals/sample: 1
こちらのコードを見るとSIMDを使って加算の順番を変えてやっているようだ。
また、組み込みのreduce
を使ってもほとんど同じ性能が得られる。すごい。
julia> @benchmark reduce(+, randoms)
BenchmarkTools.Trial:
memory estimate: 16 bytes
allocs estimate: 1
--------------
minimum time: 242.448 μs (0.00% GC)
median time: 289.671 μs (0.00% GC)
mean time: 300.357 μs (0.00% GC)
maximum time: 799.867 μs (0.00% GC)
--------------
samples: 10000
evals/sample: 1