Rubyで遊んだ日々の記録。あくまで著者視点の私的な記録なので、正確さを求めないように。
Rubyと関係ない話題にはその旨注記しているはず。なので、一見関係無いように見える話題もどこかで関係あるのかもしれません。または、注記の書き忘れかもしれません...
_ Ruby界を席巻しているfrozen string literalですが、「社会問題」、つまり「.freeze
をバカスカ付けてコードを汚すクソパッチ攻撃に耐えられない!」という意見には頷ける面もあるものの、frozen string literalが導入された場合、.dup
を付けて回ってコードを汚さないといけないことには人々は耐えられるんでしょうか?
_ そもそも.freeze
であればメソッド名が意図を示していますが、.dup
が「mutableな文字列オブジェクトがほしいの!」という意味だとメソッド名から読み取れますか?
そういう意味では.dup
の方が.freeze
より遥かに汚くコードを汚すシロモノなわけです。許しがたし!
_ というわけで、ぼくとしてはこの用途にはString.new
を強く推奨するわけですが、この場合は意図は明確であるものの、長い。むちゃくちゃ長い。これでは流行るはずがないですね。
しかし、どーせお前ら速ければなんでもいいんだろ? ああん? という気もするので、.dup
に比してString.new
が有意に速いならワンチャンあるのではないか、という気もしてきました。
# -*- frozen-string-literal: true -*- require "benchmark" puts RUBY_DESCRIPTION N = 10_000_000 Benchmark.bm(16) do |bm| bm.report("empty loop") do N.times do # 空 end end bm.report("plain literal") do N.times do "" end end bm.report(".freeze") do N.times do "".freeze end end bm.report(".dup") do N.times do "".dup end end bm.report("String.new") do N.times do String.new("") end end end
ruby 2.3.0dev (2015-10-07 trunk 52067) [x64-mswin64_100] user system total real empty loop 0.484000 0.000000 0.484000 ( 0.511896) plain literal 0.546000 0.000000 0.546000 ( 0.538358) .freeze 0.514000 0.000000 0.514000 ( 0.529058) .dup 3.978000 0.000000 3.978000 ( 3.975763) String.new 2.808000 0.000000 2.808000 ( 2.804275)
お、おお? String.new
速いじゃん! およそ40%かな。
これは凄い。
そもそもstring literalをfreezeしないといられない人々は速度のためにそういうこと言い出してるわけで、ならば誰もこの用途に.dup
を使うことはないだろう、と結論付けてもいいレベルやね。
_ ところで、frozen string literalのマジコメを理解しないRuby、つまり過去のバージョンだと、当然.dup
やString.new
はペナルティを受けるわけですが、それはどんなレベルなのか、ついでに測定してみましょう。
ruby 2.2.3p173 (2015-08-18 revision 51635) [x64-mswin64_100] user system total real empty loop 0.609000 0.000000 0.609000 ( 0.604623) plain literal 1.419000 0.000000 1.419000 ( 1.446351) .freeze 0.656000 0.000000 0.656000 ( 0.654756) .dup 5.007000 0.000000 5.007000 ( 5.035140) String.new 3.838000 0.000000 3.838000 ( 3.871682)
.freeze
の最適化が既に入ってることがわかりますね。
.dup
のペナルティはplain literalと比較しておよそ250%増し、といったところでしょうか。
一方でString.new
であれば170%増し程度で済んでいることがわかります。でもまーどっちもツラいな。
ruby 2.1.7p400 (2015-08-18 revision 51632) [x64-mswin64_100] user system total real empty loop 0.639000 0.000000 0.639000 ( 0.647037) plain literal 1.420000 0.000000 1.420000 ( 1.425081) .freeze 0.671000 0.000000 0.671000 ( 0.685040) .dup 4.883000 0.000000 4.883000 ( 4.894280) String.new 3.790000 0.000000 3.790000 ( 3.793217)
概ね2.2と同じですね。
すこーし2.2より.dup
とString.new
がそれぞれ速いようにも見えますが、気のせいでしょうかねえ。
ruby 2.0.0p647 (2015-08-18 revision 51630) [x64-mswin64_100] user system total real empty loop 0.639000 0.000000 0.639000 ( 0.639037) plain literal 1.358000 0.000000 1.358000 ( 1.389079) .freeze 1.762000 0.000000 1.762000 ( 1.802103) .dup 4.790000 0.000000 4.790000 ( 4.856278) String.new 3.634000 0.000000 3.634000 ( 3.649209)
これには.freeze
の最適化が入ってないため、plain literalと比べるとメソッド呼び出しの分遅くなっているのがわかります。
.dup
とString.new
のペナルティは2.2や2.1と同じようなものですね。
_ というわけで、結論。
.dup
使うなString.new
使え!
被捕捉アンテナ類
[Ant]
[Antenna-Julia]
[Rabbit's Antenna]
[Ruby hotlinks]