丁稚な日々 - apprenticeship days

Rubyで遊んだ日々の記録。あくまで著者視点の私的な記録なので、正確さを求めないように。
Rubyと関係ない話題にはその旨注記しているはず。なので、一見関係無いように見える話題もどこかで関係あるのかもしれません。または、注記の書き忘れかもしれません...

[直前] [最新] [直後] [Top]

Oct.7,2015 (Wed)

Revision: 1.1 (Oct.07,2015 12:58)

frozen string literalネタ

_ 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、つまり過去のバージョンだと、当然.dupString.newはペナルティを受けるわけですが、それはどんなレベルなのか、ついでに測定してみましょう。

_ まず2.2。

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%増し程度で済んでいることがわかります。でもまーどっちもツラいな。

_ 続いて2.1。

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より.dupString.newがそれぞれ速いようにも見えますが、気のせいでしょうかねえ。

_ 最後に2.0.0。

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と比べるとメソッド呼び出しの分遅くなっているのがわかります。
.dupString.newのペナルティは2.2や2.1と同じようなものですね。

_ というわけで、結論。
.dup使うなString.new使え!


被捕捉アンテナ類
[Ant] [Antenna-Julia] [Rabbit's Antenna] [Ruby hotlinks]