最近のC++では、constexpr
を使って手軽にコンパイル時計算ができます。
C++20ではstd::vector
やstd::string
などの動的なデータ構造もconstexpr
指定されたため、さらに利便性が高まりました。
コンパイル時に多少の探索を行って最適な値を発見して埋め込む、といった利用方法も現実的となりました。
一方で、constexpr
関数の評価は実質的にインタプリタ実行であり、速度が気になります。
今回はそれを調査してみました。
測定環境
- Intel Core i9 12900K (Alder Lake-S)
- 5.1 GHzくらいで動いている
- Ubuntu 20.04.3 LTS on WSL2 (Windows 11)
- g++ (Ubuntu 9.4.0-1ubuntu1~20.04.1) 9.4.0(aptでインストールしたもの)
- clang++ version 12.0.1(公式によるx86_64-unknown-linux-gnu向けプレビルドバイナリ)
- 時間の測定は何回か実行した平均
実験に使ったソースコード
#include <iostream> X unsigned long long f( unsigned long long n ) { unsigned long long x = 1234567890987654321; for( ; n--; ) { x ^= x << 7; x ^= x >> 9; } return x; } int main() { Y unsigned long long x = f( N ); std::cout << x << std::endl; }
X, Y, Nはコンパイルオプションで指定します。
g++
g++は、以下の戦略でコンパイル時計算を行うようです。
- 関数が
constexpr
指定されていなくて、変数がconstexpr
指定されていなければ、コンパイル時計算を行わない。 - 関数が
constexpr
指定されていなくて、変数がconstexpr
指定されていれば、コンパイルエラー(C++の仕様)。 - 関数が
constexpr
指定されていて、変数がconstexpr
指定されていなければ、 - 関数が
constexpr
指定されていて、変数がconstexpr
指定されていなければ、
実行時間は以下のようになりました。
X | Y | Opt | limit | N=1000000 | N=10000000 |
---|---|---|---|---|---|
なし | なし | -O0 | default | 0.16 s | 0.15 s |
なし | なし | -O2 | default | 0.17 s | 0.16 s |
constexpr | なし | -O0 | default | 0.49 s | 0.48 s |
constexpr | なし | -O2 | default | 1.08 s | 1.15 s |
constexpr | なし | -O0 | 999999999 | 1.89 s | 23.3 s |
constexpr | なし | -O2 | 999999999 | 1.97 s | 23.6 s |
constexpr | constexpr | -O0 | 999999999 | 1.94 s | 22.5 s |
constexpr | constexpr | -O2 | 999999999 | 1.92 s | 23.3 s |
普通の命令セットだと一周当たり4cycleくらいで実行可能な命令列にコンパイルされるはずなので、5GHzのCPUで動かすと1秒間に109周以上できるはずです。 107周に23秒かかっているので、コンパイルされたコードの2500倍程度遅いということがわかりました。
clang++
clang++は、変数がconstexpr
指定されている時にのみコンパイル時計算を行うようです。
実行時間は以下のようになりました。
X | Y | Opt | limit | N=1000000 | N=10000000 | N=30000000 | N=100000000 |
---|---|---|---|---|---|---|---|
なし | なし | -O0 | 999999999 | 0.16 s | 0.17 s | ||
なし | なし | -O2 | 999999999 | 0.18 s | 0.20 s | ||
constexpr | なし | -O0 | 999999999 | 0.20 s | 0.18 s | ||
constexpr | なし | -O2 | 999999999 | 0.17 s | 0.18 s | ||
constexpr | constexpr | -O0 | 999999999 | 0.83 s | 6.8 s | ||
constexpr | constexpr | -O2 | 999999999 | 0.85 s | 6.8 s | 20 s | 67 s |
108周に67秒かかっているので、コンパイルされたコードの700倍程度遅いということがわかりました。
まとめ
コンパイル時計算(constexpr
関数の評価)は機械語コードの実行と比べて三桁(オーダーで1000倍)程度遅いことがわかりました。