Feature #15435
openFloat の Infinity に生成済みの値を使用する
Description
Float の Infinity はしばしば使われると思うのですが、Flonum が有効でも即値に
ならないので、演算の結果や C から DBL2NUM で返却される場合などに毎回オブ
ジェクトが生成されます。
Ruby リポジトリーで、Infinity オブジェクトが生成されると明確に分かる場所を簡
単に調べた限りでは20数箇所ありました。
$ grep 'DBL2NUM.*HUGE_VAL' $(git ls-files | awk '/\.c$/ && !/^(spec|ext\/-test-)\//') | wc -l
21
$ egrep -- '-Float::INFINITY' $(git ls-files | egrep '^(lib|ext)/.*\.rb$') | wc -l
2
それで、Infinity の場合は生成済みのオブジェクトを返すようにしてはどうでしょ
うか。
パッチを添付します。添付のベンチマークを benchmark-driver で実行した限りでは、
演算結果が Infinity になる場合は性能が向上し、そうでない場合は有意な差はない
(計測の度にかなりばらつきがあるが概ね10%以内の差におさまる) ようでした。
Calculating -------------------------------------
compare-ruby built-ruby
positive_infinity 34.531M 62.540M i/s - 3.000M times in 0.195822s 0.107997s
negative_infinity 42.581M 94.234M i/s - 3.000M times in 0.159061s 0.071766s
flonum 142.010M 150.967M i/s - 3.000M times in 0.047544s 0.045120s
heap 33.952M 34.629M i/s - 3.000M times in 0.199321s 0.195063s
Comparison:
positive_infinity
built-ruby: 62540441.7 i/s
compare-ruby: 34530877.6 i/s - 1.81x slower
negative_infinity
built-ruby: 94234135.1 i/s
compare-ruby: 42580998.6 i/s - 2.21x slower
flonum
built-ruby: 150967185.7 i/s
compare-ruby: 142010146.8 i/s - 1.06x slower
heap
built-ruby: 34629459.1 i/s
compare-ruby: 33952081.8 i/s - 1.02x slower
Files
Updated by shuujii (Shuji KOBAYASHI) almost 6 years ago
- Subject changed from Float の Infinity ni to Float の Infinity ...
Updated by shuujii (Shuji KOBAYASHI) almost 6 years ago
- File use-predefined-infinity.patch use-predefined-infinity.patch added
- File benchmark.yml benchmark.yml added
- Tracker changed from Bug to Feature
- Subject changed from Float の Infinity ... to Float の Infinity に生成済みの値を使用する
- ruby -v deleted (
ruby 2.6.0rc2 (2018-12-15 trunk 66408) [x86_64-linux]) - Backport deleted (
2.4: UNKNOWN, 2.5: UNKNOWN)
Updated by shuujii (Shuji KOBAYASHI) almost 6 years ago
- Description updated (diff)
Updated by ko1 (Koichi Sasada) almost 6 years ago
(コードのご提案時には、アイディア(アルゴリズム)を書いて頂けるとありがたいです)
パッチを見たところ、Flonum 生成時に、範囲外で heap から生成する直前に isinf()
でチェックして、inf であれば、事前に allocate した inf オブジェクトを返す、と理解しました。
if (isinf(d)) {
return d < 0 ? rb_float_negative_infinity : rb_float_positive_infinity;
}
というわけで、isinf(d)
が入るのを許容できるか、という議論になるんじゃないかと思います。
で、このパスでは heap allocate が入るので、isinf(d)
check の負荷は問題にならなそうだから、いいような気がします。
でも、ベンチマークで 2% 負荷が増えてるんですね、意外と大きいのかな...。heap にはほとんど落ちないと思っているんですが、heap に落ちる率がわかると、もうちょっと勢いよく、行きましょう! って言えそうです。アプリをお持ちだったりしますか?
以下、その他のコメントです。
rb_global_variable(&rb_float_negative_infinity);
rb_float_negative_infinity = rb_float_new_in_heap(-HUGE_VAL);
(1) rb_global_variable
よりも、rb_gc_register_mark_object
でオブジェクト自体を登録したほうが良いです。
(2) Infinity じゃなくて、いっそ NegativeInfinity という定数も用意してしまったらどうだろう。
benchmark-driver でループ回数指定じゃないモードの方が、この場合良さそうです(どうやるのかよく覚えてない)。
Updated by shuujii (Shuji KOBAYASHI) almost 6 years ago
確認していただいてありがとうございます。
# 順番がいろいろ前後して回答します。
以下、その他のコメントです。
rb_global_variable(&rb_float_negative_infinity); rb_float_negative_infinity = rb_float_new_in_heap(-HUGE_VAL);
(1) rb_global_variable よりも、rb_gc_register_mark_object でオブジェクト自体を登録したほうが良いです。
なるほどー。オブジェクトに対する間接参照が減るということですね (これは
doc/extention.rdoc に載っていると嬉しいのではと思って調べて見たところ、#9894
で提案があったみたいですね)。
修正したパッチを添付します。
(2) Infinity じゃなくて、いっそ NegativeInfinity という定数も用意してしまったらどうだろう。
これは Float::NEGATIVE_INFINITY を定義するということですよね? それは少し思
いました。Java や JavaScript では POSITIVE_INTINITY と NEGATIVE_INFINITY の
両方が定義されているみたいです。
(コードのご提案時には、アイディア(アルゴリズム)を書いて頂けるとありがたいです)
すいません、以降気を付けます。
パッチを見たところ、Flonum 生成時に、範囲外で heap から生成する直前に isinf() でチェックして、inf であれば、事前に allocate した inf オブジェクトを返す、と理解しました。
その通りです。
heap にはほとんど落ちないと思っているんですが、heap に落ちる率がわかると、もうちょっと勢いよく、行きましょう! って言えそうです。アプリをお持ちだったりしますか?
特にアプリの持ち合わせはありません。
でも、ベンチマークで 2% 負荷が増えてるんですね、意外と大きいのかな...。
掲載した結果では遅くなっているのではなくて速くなっているんですよね...。ベン
チマークの結果はかなりばらつきが多かったのでたまたまだと思います。
benchmark-driver でループ回数指定じゃないモードの方が、この場合良さそうです(どうやるのかよく覚えてない)。
確かに、ループ回数を指定しない (YAML で loop_count を指定しない) ほうが結果
はやや安定するようです。ただ、計測環境が悪いのかやり方がまずいのか、それでも
結果は十分に安定しないというか、同一バイナリで比較してみてもほぼ毎回数%の差が
付くので、C の分岐一つなどの影響を厳密に計測するのは今回のやり方では難しいの
かもしれません。何か良い方法はあるでしょうか... (皆さんこういうときはどうやっ
て計測しているのでしょうか...)。