いさぽん部屋(isapon.com)

ゲーム系プログラマによる特に方針のないブログ。技術系とカレー、ラーメンネタ多めだったはずが、最近はダイエットネタ多め。

【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

結果はうまくいきました。思った以上に最適化がよくできている!これでいろいろとプログラムの幅が広がるぞ!