pow(x, 1/32.), pow(x, 1/48.), pow(x, 1/72.)のベンチマーク
はじめに
GCC-6からpow(x, 1/32.)などがsqrt()を使って最適化されるようになった[1]。 pow(x, 1/32.), pow(x, 1/48.), pow(x, 1/72.)のベンチマークを行う[2]。 C++11を使ってみた。
sqrt()はハードウエアがあるが、使われているかどうかは不明。cbrt()のハードウエアは普通のCPUにはない[3,4]。
この文章の最新版はGistに置いてある。
ベンチマークコード
pow48.cppがベンチマークコード。
Makefileを用意した。
make clean; make
でGNU C++ (g++
)とIntel C++ (icpc
)とを試すことができる。
pow48.cppで計時は C++11のstd::chronoを使っている[5]。
Results
g++の新旧バージョンが両方とも入っているマシンを持っていない。
ライセンスが高価なicpc
は少数のマシンにしかインストールされていない。
Intel Xeon E5-2680 v3 (g++ version 4.8.3)
g++ version 4.8.3ではまだpow(x, 1/32.)の最適化ができない。
$ grep Xeon /proc/cpuinfo | head -1
model name: Intel(R) Xeon(R) CPU E5-2680 v3 @ 2.50GHz
$ make clean
rm -f *.o core *.s *.ps pow48_g++ pow48_icpc 00pow48.html
$ make
g++ --version | head -1
g++ (GCC) 4.8.5 20150623 (Red Hat 4.8.5-4)
g++ -std=c++11 -Ofast pow48.cpp -o pow48_g++
./pow48_g++
32,pow : 3870 ms, 1.93965617861778
32,sqrt*5 : 720 ms, 1.93965617861778
48,pow : 3737 ms, 1.555308347057671
48,cbrt,sqrt*4 : 3917 ms, 1.555308347057671
72,pow : 3741 ms, 1.342386786199955
72,cbrt*2,sqrt*4: 5772 ms, 1.342386786199955
Intel Celeron 1037U 1.80GHz (g++ version 4.8.4)
Ubuntu 14.04.4 LTSのg++ 4.8.4-2ubuntu1~14.04.1でもpow(x, 1/32.)の最適化ができない。
$ grep Celeron /proc/cpuinfo | head -1
model name: Intel(R) Celeron(R) CPU 1037U @ 1.80GHz
$ make clean
rm -f *.o core *.s *.ps *.ps~ pow48_g++ pow48_icpc pow48f_gfortran 00pow48.html
$ make
g++ --version | head -1
g++ (Ubuntu 4.8.4-2ubuntu1~14.04.1) 4.8.4
g++ -std=c++11 -Ofast pow48.cpp -o pow48_g++
./pow48_g++
32,pow : 7597 ms, 1.93965617861778
32,sqrt*5 : 1327 ms, 1.93965617861778
48,pow : 7241 ms, 1.555308347057671
48,cbrt,sqrt*4 : 7381 ms, 1.555308347057671
72,pow : 7243 ms, 1.342386786199955
72,cbrt*2,sqrt*4: 10916 ms, 1.342386786199955
Intel Xeon X5650 (g++ version 6.0.0)
すこし古いXeon。
GNU C++ (g++
)でpow(x, 1/32.)が最適化されている様子がわかる。
Intel C++ (icpc
)がさらに速い実行形式を作ってくれる。
icpc
のアセンブラ出力を見るとsvml
ってたくさん出でくる[6]。
SIMD命令がうまく使われて速くなっていたりするのかも。
$ grep Xeon /proc/cpuinfo | head -1
model name: Intel(R) Xeon(R) CPU X5650 @ 2.67GHz
$ make clean
rm -f *.o core *.s *.ps pow48_g++ pow48_icpc 00pow48.html
$ make
g++ --version | head -1
g++ (GCC) 6.0.0 20160306 (experimental)
g++ -std=c++11 -Ofast pow48.cpp -o pow48_g++
./pow48_g++
32,pow : 2019 ms, 1.93965617861778
32,sqrt*5 : 1884 ms, 1.93965617861778
48,pow : 5366 ms, 1.555308347057671
48,cbrt,sqrt*4 : 5406 ms, 1.555308347057671
72,pow : 5516 ms, 1.342386786199955
72,cbrt*2,sqrt*4: 5571 ms, 1.342386786199955
icpc -V |& head -1
Intel(R) C++ Intel(R) 64 Compiler XE for applications running on Intel(R) 64, Version 15.0.3.187 Build 20150407
icpc -std=c++11 -O3 pow48.cpp -o pow48_icpc
./pow48_icpc
32,pow : 1505 ms, 1.93965617861778
32,sqrt*5 : 1755 ms, 1.93965617861778
48,pow : 1359 ms, 1.555308347057671
48,cbrt,sqrt*4 : 1746 ms, 1.555308347057671
72,pow : 1358 ms, 1.342386786199955
72,cbrt*2,sqrt*4: 2079 ms, 1.342386786199955
$ icpc -std=c++11 -O3 pow48.cpp -S
$ grep svml pow48.s
:
call __svml_pow2 #44.38
call __svml_cbrt2 #49.38
:
Intel Core i5 (Ivy Bridge) 2.5 GHz on MacBook Pro (Retina, 13-inch, Late 2012):
このマシンだけ生成される擬似乱数列が異なる。Mac OS Xのライブラリの関係か???
$ system_profiler | grep 'Processor '
Processor Name: Intel Core i5
Processor Speed: 2.5 GHz
$ make clean
rm -f *.o core *.s *.ps pow48_g++ pow48_icpc 00pow48.html
$ make
g++ --version | head -1
g++ (GCC) 6.0.0 20160124 (experimental)
g++ -std=c++11 -Ofast pow48.cpp -o pow48_g++
./pow48_g++
32,pow : 796 ms, 1.919878048947668
32,sqrt*5 : 784 ms, 1.919878048947668
48,pow : 2321 ms, 1.544717602206269
48,cbrt,sqrt*4 : 2342 ms, 1.544717602206269
72,pow : 2327 ms, 1.336285933906074
72,cbrt*2,sqrt*4: 2340 ms, 1.336285933906074
Intel Xeon E5-2637 v2 3.50GHz
このマシンのg++はversion 4.4.7と古くて、C++11のコードをコンパイルできない。
Intel C++ (icpc
)の結果のみ。
$ grep Xeon /proc/cpuinfo | head -1
model name: Intel(R) Xeon(R) CPU E5-2637 v2 @ 3.50GHz
$ make clean
rm -f *.o core *.s *.ps *.ps~ pow48_g++ pow48_icpc pow48f_gfortran 00pow48.html
$ g++ --version | head -1
g++ (GCC) 4.4.7 20120313 (Red Hat 4.4.7-16)
$ make pow48_icpc
icpc -V |& head -1
Intel(R) C++ Intel(R) 64 Compiler for applications running on Intel(R) 64, Version 16.0.1.150 Build 20151021
icpc -std=c++11 -O3 pow48.cpp -o pow48_icpc
./pow48_icpc
32,pow : 1032 ms, 1.93965617861778
32,sqrt*5 : 621 ms, 1.93965617861778
48,pow : 1013 ms, 1.555308347057671
48,cbrt,sqrt*4 : 943 ms, 1.555308347057671
72,pow : 1013 ms, 1.342386786199955
72,cbrt*2,sqrt*4: 1321 ms, 1.342386786199955
g++
にもicpc
にも-march=native
という
コンパイルオプションを指定することができる。
次のように、このマシンとコンパイラの組み合わせでのみ著しく高速化した。
$ icpc -std=c++11 -O3 -march=native pow48.cpp -o pow48_icpc
$ ./pow48_icpc
32,pow : 631 ms, 1.93965617861778
32,sqrt*5 : 621 ms, 1.93965617861778
48,pow : 630 ms, 1.555308347057671
48,cbrt,sqrt*4 : 821 ms, 1.555308347057671
72,pow : 631 ms, 1.342386786199955
72,cbrt*2,sqrt*4: 1087 ms, 1.342386786199955
まとめ
GCC-6でpow(x, 1/32.)が最適化されるのを確認した。
Intel C++ (icpc
)がさらに速い実行形式を作ってくれるようだ。
反省
- より速くできるコンパイルオプションがあるかもしれない。
- 超ひさしぶりにC++のtemplateを使ったら鼻血が出た。
- アセンブラを読めるようにならないといけない。
- SIMD, SSE, AVXを理解しないといけない。
参考文献
- Kyrill Tkachov: [PATCH][tree-ssa-math-opts] Expand pow (x, CONST) using square roots when possible https://gcc.gnu.org/ml/gcc-patches/2015-05/msg00071.html
- chunjp: pow(x, 1/48.0)を高速化……うーん…… https://twitter.com/chunjp/status/596940789495635968
- 高木、田中: 立方根計算のハードウェアアルゴリズムについて http://ci.nii.ac.jp/naid/110003226017
- 高木、南: 減算シフト型立方根計算回路 http://ci.nii.ac.jp/naid/110003226017
- 津田伸秀: std::chronoはC++11で追加された時間計測の枠組みだ http://vivi.dyndns.org/tech/cpp/timeMeasurement.html#chrono
- SVML (Short Vector Mathematical Library) 操作の組込み関数 http://www.xlsoft.com/jp/products/intel/compilers/manual/cpp_all_os/GUID-14A424F2-8AAE-41C0-B4A9-A494ADBD3B29.htm