浮動小数点数を足すだけのプログラムがC言語よりJuliaのほうが速い、について

なんかすごく前に話題になっていたので、確かめてみました。

数値計算に強いプログラミング言語と言えば従来FortranC言語でしたが、近年Pythonのように書けて実用上十分な速度を達成できるJuliaが人気を集めているようです*1

Juliaは動的言語ですが、(1) 型推論を行うためfor文などでいちいち型検査するPythonより速い (2) JITコンパイルされるので関数に切り出した場合に高速、といった点が高速化に寄与しています。

C言語で書いたほうが速いに決まっている」などの決めつけはよくないので、実際に計測してみます。

そもそもベンチマークソースコードが提示されていないので議論のしようがないのですが、「浮動小数点数を足すだけ」という文章を私が解釈したコードは以下の通りになります。

function test()
  a = zeros(Float64, 1000)
  a[1] = 1e-16

  sum = 1.0

  for j = 1:10000000, i = 1:1000
    sum += a[i]
  end

  return sum
end

test()
double test() {
  double a[1000] = {};
  a[0] = 1e-16;

  double sum = 1.0;

  for( unsigned long long j = 0; j < 10000000; ++j ) {
    for( unsigned long long i = 0; i < 1000; ++i ) {
      sum += a[i];
    }
  }

  return sum;
}

int main() {
  return test();
}

メモリアクセスの速度が測りたいわけではないので、配列のサイズはL1キャッシュに乗る程度の大きさとしました。

環境

  • gcc 9.1.0、-O3をつけてコンパイル
  • julia version 1.5.2
  • Intel(R) Xeon(R) CPU E5-2603 v3 @ 1.60GHz(addsd命令は3cycleレイテンシ)

結果

C言語

  • 30.1G cycles
  • 35.1G instructions
  • 5.0G branches
  • 18.8 seconds

Julia

  • 31.9G cycles
  • 61.5G instructions
  • 20.3G branches
  • 19.1 seconds

考察

数値上はJuliaのほうがほんのわずかに遅くなっていますが、C言語は事前コンパイルが必要でそれに0.2秒ほどかかることを考慮に入れると速度は同じと言ってよいでしょう。

そもそもこのベンチマークの速度はレイテンシに支配されているため、それなりの最適化がかかっていれば速度が変わるわけはないのです。

C言語コードをコンパイルした結果は以下のようになっており、ループ一周は7命令です。アセンブリ上でのループ一周は元のソースコードの二周分に相当しています(アンローリングされています)。 この中には直列に依存したaddsd命令が2つあるので、ループ一周当たり6cycleかかることになります。 C言語コンパイルした結果のperfのサイクル数、命令数、分岐命令数は、これらと整合します。

.L3:
        movsd   xmm1, QWORD PTR [rax]
        add     rax, 16
        addsd   xmm1, xmm0
        movsd   xmm0, QWORD PTR [rax-8]
        addsd   xmm0, xmm1
        cmp     rdx, rax
        jne     .L3

一方Juliaの方はサイクル数こそほぼ一緒ですが、命令数がかなり多くなっています。 これは配列の境界検査を毎回行っているためです。 C言語と条件をそろえ、配列境界検査を行わないようにするため、@inboundsをつけてみると、以下のように命令数と分岐命令数が減ります。

  • 31.9G cycles
  • 41.5G instructions
  • 10.3G branches
  • 19.1 seconds

命令数は一ループ当たり2命令、分岐命令数は一ループ当たり1命令減りました。この減った分は、比較命令と分岐命令ということになります。

分岐命令数から察するにループアンローリングは行われていないようですが、Juliaで書いたコードをJITコンパイルした結果はほぼ最適です。

まとめ

浮動小数点数を足すだけのプログラム」の速度がC言語で書いた場合とJuliaで書いた場合で変わることはありませんでした。

*1:ほかの言語では数値計算ができないとか遅いとか言っているわけではありませんが、あまり見かけないですね。最近はC++で書かれたソフトウェアもちらほら見かけ始めました(知っているところではALPSとか)。