【c++】 idiv命令が最適化されるか試してみた
x86互換のCPUには割り算命令(idiv)を行うと、同時に余りも求めてくれます。なのでどちらも求める場合は1命令で求めることができるわけです。例えば以下のようなプログラムを書いた場合idiv命令1回でdivとmodを求めることができます。
std::pair<int64_t, int64_t> div(int64_t operand1, int64_t operand2) { auto div = operand1 / operand2; auto mod = operand1 % operand2; return { div, mod }; }
なので、以下のような呼び出しを行った場合、idiv1回とadd1回で済む計算になります。
auto [d, m] = div(x, y); int64_t a = d + m;
感覚的には次のようになるかと思います。
mov rax, operand1 mov rbx, operand2 cqo ; rax => rdx:rax idiv rbx ; rax = div, rdx = mod add rax, edx
もし、予測通りのコードを生成してくれたらプログラムの書き方もいろいろと幅が広がりそうです。
clangでコンパイルしてみる
最適化オプションをつけないとちゃんと意図通りにいかないかなーと思い、とりあえず最適化オプションを有効にしてコンパイルしてみます。
clang++ -S -mllvm --x86-asm-syntax=intel -O2 -std=c++17 mul.cpp
結果は以下の通り。addではなく結果をrsiに格納するためにleaを使っていますが予想通りの結果を生成してくれました。
mov rcx, rax mov rax, r15 cqo idiv rcx lea rsi, [rdx + rax]
idivの最適化を行ってくれる。ということで。
imulの最適化もやってみた
ついでに掛け算もやってみる。
掛け算命令(imul)は結果を倍精度(rdx:rax)に拡張して結果を返します。 なので以下のように書いた場合は1回の掛け算で済むはずです。
std::pair<int64_t, int64_t> mul(int64_t _operand1, int64_t _operand2) { T h = (__int128)_operand1 * (__int128)_operand2 >> (sizeof(int64_t) * 8); T l = (__int128)_operand1 * (__int128)_operand2; return { h, l }; } auto [d, m] = mul(v1, v2); int64_t a = (d + m);
以下のようになってくれると良いなぁ…と。
mov rax, operand1 mov rdx, operand2 imul rdx add rax, rdx
結果はうまくいきました。思った以上に最適化がよくできている!これでいろいろとプログラムの幅が広がるぞ!