x ** 2 は遅い
変数 x
に Integer とか Float とかの数値が入っているとする。
x
の 2 乗の計算は,
x ** 2
と書けるが,もしできるだけ高速に計算させたいなら
x * x
と書いたほうがいい。
後者のほうが断然速い。
というか前者はめちゃんこ遅い。
ベンチマークテスト
まず
gem install benchmark_driver
して benchmark_driver をインストールしておき,
prelude: |
x = 3.0
benchmark:
- "x ** 2"
- "x * x"
という YAML ファイルを用意して,
benchmark-driver square.yaml
とやると比較できる。
ruby 2.7.1p83 (2020-03-31 revision a0c7c23c9c) [x86_64-darwin17]
で計測してみたところ,結果は以下のとおり。
x * x: 92940175.9 i/s
x ** 2: 15605744.0 i/s - 5.96x slower
わお!
まあ,遅いったって,毎秒 1500 万回実行できるわけだから,この遅さが問題になるのは大量の数値計算をやる場合に限られるわけだけれど1。
Ruby 3.0 で改善
Float に対する ** 2
を特別扱いして速く計算するような変更が入った。
Ruby 3.0.0-preview1 で確認できる。
rbenv で Ruby を入れている方は benchmark_driver で簡単にテストできる。
さきほどと同じ YAML ファイルに対して,こんどは
benchmark-driver square.yaml --rbenv "2.7.2;3.0.0-preview1"
とする。
結果は以下のとおり。
x ** 2
3.0.0-preview1: 37039739.4 i/s
2.7.2: 16010580.7 i/s - 2.31x slower
x * x
2.7.2: 91881050.6 i/s
3.0.0-preview1: 88138626.5 i/s - 1.04x slower
x * x
で,むしろ 3.0.0-preview1
のほうが遅いが,これは誤差の範囲。
x ** 2
のほうは,2.3 倍速になった。
感想
まあそれでも x * x
のほうが x ** 2
より倍以上速いわけだけど,それでも重要な進化だと思う。
世の中に x ** 2
のようなコードはたくさんあって,それらが何もしなくても速くなってくれるわけだから。
場合によっては速度を犠牲にしてあえて ** 2
を使いたいときもある。
それは 2 乗したいものが変数に入っていなくて,式のとき。
まあ,こういうイメージかな:
items.map.with_index{ |item, i| (a * item.value + b[i]) ** 2 }
これを
items.map.with_index{ |item, i| (a * item.value + b[i]) * (a * item.value + b[i]) }
などとすると,冗長なばかりか,おそらくかえって速度を落とすことになる。
そこで,いったん式の値を変数に入れて
items.map.with_index{ |item, i| x = a * item.value + b[i]; x * x }
と書くことになる。
速度が最優先ならこう書くところだが,「速度もそこそこ求めるけど簡潔さも欲しい」といった場合は ** 2
を使うこともあるだろう。
-
プログラムを高速化したいとき,ベンチマークテストをやってきちんと数字を追うことが大事なのと,プログラム全体の実行時間を左右しない箇所の最適化に血道を上げても無意味,ということは肝に銘じておきたい。 ↩