LoginSignup
4

More than 5 years have passed since last update.

Rubyで乗算/除算/剰余にビット演算を使うと却って遅くなるっぽい

Posted at

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

原因は…

原因はまだ調べてない!

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4