この質問は、「C ++ vs Fortran for HPC」への回答で最近出された2つの議論の延長です。そして、それは質問よりも少し挑戦です...
Fortranを支持する最もよく耳にする引数の1つは、コンパイラーが優れていることです。ほとんどのC / Fortranコンパイラは同じバックエンドを共有するため、両方の言語で意味的に同等のプログラム用に生成されたコードは同一である必要があります。ただし、コンパイラが最適化するのはC / Fortranの方が多かれ少なかれ簡単だと主張することができます。
そこで、簡単なテストを試すことにしました。daxpy.fとdaxpy.cのコピーを入手し、gfortran / gccでコンパイルしました。
daxpy.cはdaxpy.fのf2c変換(自動生成コード、見苦しい)なので、そのコードを取り、それを少しクリーンアップしました(daxpy_cに会います)。
for ( i = 0 ; i < n ; i++ )
dy[i] += da * dx[i];
最後に、gccのベクトル構文を使用して書き直しました(daxpy_cvecと入力します)。
#define vector(elcount, type) __attribute__((vector_size((elcount)*sizeof(type)))) type
vector(2,double) va = { da , da }, *vx, *vy;
vx = (void *)dx; vy = (void *)dy;
for ( i = 0 ; i < (n/2 & ~1) ; i += 2 ) {
vy[i] += va * vx[i];
vy[i+1] += va * vx[i+1];
}
for ( i = n & ~3 ; i < n ; i++ )
dy[i] += da * dx[i];
長さ2のベクトル(SSE2で許可されているすべてのベクトル)を使用し、一度に2つのベクトルを処理することに注意してください。これは、多くのアーキテクチャでは、ベクトル要素よりも多くの乗算ユニットを使用できるためです。
すべてのコードは、フラグ「-O3 -Wall -msse2 -march = native -ffast-math -fomit-frame-pointer -malign-double -fstrict-aliasing」を使用してgfortran / gccバージョン4.5を使用してコンパイルされました。私のラップトップ(Intel Core i5 CPU、M560、2.67GHz)では、次の出力が得られました。
pedro@laika:~/work/fvsc$ ./test 1000000 10000
timing 1000000 runs with a vector of length 10000.
daxpy_f took 8156.7 ms.
daxpy_f2c took 10568.1 ms.
daxpy_c took 7912.8 ms.
daxpy_cvec took 5670.8 ms.
したがって、元のFortranコードは8.1秒以上かかり、その自動変換には10.5秒かかります。素朴なC実装では7.9で、明示的にベクトル化されたコードでは5.6でわずかに短縮されます。
Fortranは、単純なC実装よりもわずかに遅く、ベクトル化されたC実装よりも50%遅くなります。
だからここに質問があります:私はネイティブCプログラマーであり、そのコードで良い仕事をしたと確信していますが、Fortranコードは1993年に最後に触れたため、少し古くなっているかもしれません。私はFortranでのコーディングは他の人ほど快適ではないと感じているので、誰かがより良い仕事をすることができますか?つまり、2つのCバージョンのどれよりも競争力がありますか?
また、誰かがこのテストをicc / ifortで試すことはできますか?ベクトル構文はおそらく動作しませんが、素朴なCバージョンがそこでどのように動作するかを知りたいと思います。同じことは、xlc / xlfが横になっている人にも当てはまります。
ここにソースとMakefileをアップロードしました。正確なタイミングを取得するには、test.cのCPU_TPSをCPUのHz数に設定します。バージョンの改善点を見つけたら、ここに投稿してください!
更新:
オンラインでファイルにstaliのテストコードを追加し、Cバージョンを追加しました。前のテストと一貫性を保つために、長さ10'000のベクトルで1'000'000ループを実行するようにプログラムを変更しました(また、staliのオリジナルのように、マシンが長さ1'000'000'000のベクトルを割り当てることができなかったためコード)。数値が少し小さくなったため、オプションを使用-par-threshold:50
して、コンパイラを並列化する可能性を高めました。使用されるicc / ifortバージョンは12.1.2 20111128であり、結果は次のとおりです。
pedro@laika:~/work/fvsc$ OMP_NUM_THREADS=1 time ./icctest_c
3.27user 0.00system 0:03.27elapsed 99%CPU
pedro@laika:~/work/fvsc$ OMP_NUM_THREADS=1 time ./icctest_f
3.29user 0.00system 0:03.29elapsed 99%CPU
pedro@laika:~/work/fvsc$ OMP_NUM_THREADS=2 time ./icctest_c
4.89user 0.00system 0:02.60elapsed 188%CPU
pedro@laika:~/work/fvsc$ OMP_NUM_THREADS=2 time ./icctest_f
4.91user 0.00system 0:02.60elapsed 188%CPU
要約すると、結果は、すべての実用的な目的で、CバージョンとFortranバージョンの両方で同一であり、両方のコードが自動的に並列化します。前のテストと比較した高速時間は、単精度浮動小数点演算の使用によるものであることに注意してください!
更新:
私はここで証明の負担がどこに行くのか本当に好きではありませんが、私はCでstaliの行列乗算の例を再コーディングし、それをウェブ上のファイルに追加しました。1つと2つのCPUのトリプルループの結果は次のとおりです。
pedro@laika:~/work/fvsc$ OMP_NUM_THREADS=1 time ./mm_test_f 2500
triple do time 3.46421700000000
3.63user 0.06system 0:03.70elapsed 99%CPU
pedro@laika:~/work/fvsc$ OMP_NUM_THREADS=1 time ./mm_test_c 2500
triple do time 3.431997791385768
3.58user 0.10system 0:03.69elapsed 99%CPU
pedro@laika:~/work/fvsc$ OMP_NUM_THREADS=2 time ./mm_test_f 2500
triple do time 5.09631900000000
5.26user 0.06system 0:02.81elapsed 189%CPU
pedro@laika:~/work/fvsc$ OMP_NUM_THREADS=2 time ./mm_test_c 2500
triple do time 2.298916975280899
4.78user 0.08system 0:02.62elapsed 184%CPU
ことに注意してくださいcpu_time
FortranでCPU時間ではなく、壁時計時間をmeasuersので、私は中のコールを包んだtime
2つのCPUのためにそれらを比較します。Cバージョンが2つのコアで少し良くなることを除いて、結果に実際の違いはありません。
matmul
この組み込み関数はCでは使用できないため、コマンドについてはもちろんFortranでのみ使用できます。
pedro@laika:~/work/fvsc$ OMP_NUM_THREADS=1 time ./mm_test_f 2500
matmul time 23.6494780000000
23.80user 0.08system 0:23.91elapsed 99%CPU
pedro@laika:~/work/fvsc$ OMP_NUM_THREADS=2 time ./mm_test_f 2500
matmul time 26.6176640000000
26.75user 0.10system 0:13.62elapsed 197%CPU
ワオ。それは絶対にひどいです。誰かが私が間違っていることを見つけたり、なぜこの組み込み関数がまだなぜ良いことなのかを説明できますか?
dgemm
インテルMKLの同じ関数のライブラリー呼び出しであるため、ベンチマークには呼び出しを追加しませんでした。
将来のテストでは、FortranよりもCの方が遅いことが知られている例を提案できますか?
更新
matmul
組み込み関数が小さな行列の明示的な行列積よりも「マグニチュード」であるというstaliの主張を検証するために、私は独自のコードを修正し、両方の方法を使用してサイズ100x100の行列をそれぞれ10'000倍に乗算しました。1つと2つのCPUでの結果は次のとおりです。
pedro@laika:~/work/fvsc$ OMP_NUM_THREADS=1 time ./mm_test_f 10000 100
matmul time 3.61222500000000
triple do time 3.54022200000000
7.15user 0.00system 0:07.16elapsed 99%CPU
pedro@laika:~/work/fvsc$ OMP_NUM_THREADS=2 time ./mm_test_f 10000 100
matmul time 4.54428400000000
triple do time 4.31626900000000
8.86user 0.00system 0:04.60elapsed 192%CPU
更新
Grisuは、最適化なしでgccが複素数の演算をライブラリ関数呼び出しに変換し、gfortranがいくつかの命令でインライン化することを指摘しています。
オプション-fcx-limited-range
が設定されている場合、Cコンパイラは同じコンパクトなコードを生成します。つまり、コンパイラは、中間値の潜在的なオーバーフロー/アンダーフローを無視するように指示されます。このオプションは、gfortranでデフォルトで何らかの形で設定されており、誤った結果になる可能性があります。-fno-cx-limited-range
gfortranの強制は何も変更しませんでした。
したがって、これは実際には数値計算にgfortranを使用することに対する議論です。正しい結果が浮動小数点範囲内にある場合でも、複素数値の演算はオーバーフロー/アンダーフローする可能性があります。これは実際にはFortran標準です。gcc、または一般的なC99では、特に指定がない限り、デフォルトでは厳密に(IEEE-754準拠に準拠して)処理が行われます。
注意: FortranコンパイラーがCコンパイラーよりも優れたコードを生成するかどうかが主要な問題であったことに注意してください。これは、ある言語が他の言語よりも優れているという一般的なメリットについて議論する場所ではありません。私が本当に興味を持っているのは、明示的なベクトル化を使用してCの1つと同じくらい効率的なgfortranを生成するためのgfortranを同軸化する方法を見つけることができれば、SIMD最適化のみにコンパイラに依存しなければならない問題を例示するか、またはFortranコンパイラが対応するCコンパイラよりも優れている場合。