CやC++を扱う人にとって、乗算/除算/剰余は実行速度面で無闇に使ってはいけない演算子で、
b = a % 8;
とか見かけた日にはモヤモヤが止まらないと思う。
実質、コンパイラがこっそり最適化してくれてたり、実行頻度が低いコードだとほぼ差は無かったりするんだけれども。
そんな人がRubyを扱い始めて、同じようにビット演算を使って計算して「今日も良いコードを書いたぜ」と思ってたらむしろ悪化している、というお話。
測ってみた
こんな感じのコード。
require 'benchmark'
Benchmark.bm(7) do |x|
# 乗算
x.report("mul_nrm: ") { 10_000_000.times { 123 * 4 } }
x.report("mul_bit: ") { 10_000_000.times { 123 << 2 } }
# 除算
x.report("div_nrm: ") { 10_000_000.times { 123 / 4 } }
x.report("div_bit: ") { 10_000_000.times { 123 >> 2 } }
# 剰余
x.report("mod_nrm: ") { 10_000_000.times { 123 % 4 } }
x.report("mod_bit1:") { 10_000_000.times { 123 & 3 } }
x.report("mod_bit2:") { 10_000_000.times { 123 & ((1<<2)-1) } }
end
こんな結果に
user system total real
mul_nrm: 0.590000 0.000000 0.590000 ( 0.598509)
mul_bit: 0.680000 0.000000 0.680000 ( 0.681477)
div_nrm: 0.640000 0.000000 0.640000 ( 0.633073)
div_bit: 0.690000 0.000000 0.690000 ( 0.693372)
mod_nrm: 0.590000 0.000000 0.590000 ( 0.595054)
mod_bit1: 0.670000 0.000000 0.670000 ( 0.670466)
mod_bit2: 1.020000 0.000000 1.020000 ( 1.012992)
123 & ((1<<2)-1)
とか下位何ビットが欲しいのか分かるように気を利かせて書いた日にはもう…。
ちなみにCの場合
一番極端なところだけ。gcc -O0
でコンパイルした。
それでもa = 123 % 4
なんてしてると書いた通りのアセンブラが生成されないので、
a
を順次更新するようにちょっと変えている。厳密じゃないけど、ざっくりした比較ということで。
#include <stdio.h>
#include <time.h>
int main(void)
{
int a = 123456789;
clock_t start_time = clock();
for(int i = 0; i < 1000000; ++i)
{
// 計測対象の計算 * 10回
}
double elapsed_time = clock() - start_time;
printf("%f[sec]\n", elapsed_time / CLOCKS_PER_SEC);
return 0;
}
計算式 | 処理時間[sec] |
---|---|
a = a % 4 |
0.096101 |
a = a & ((1<<2)-1) |
0.018355 |
原因は…
原因はまだ調べてない!