Techioz Blog

空の配列とハッシュがインターンされないのはなぜですか?

概要

最近、Ruby は [] と {} が共通の共有オブジェクトを指すようにインターンされるように最適化していないことを知りました。デモ:

irb(main):001:0> [].object_id
=> 70284401361960
irb(main):002:0> [].object_id
=> 70284392762340 # different
irb(main):003:0> [].object_id
=> 70284124310100 # different

irb(main):005:0> {}.object_id
=> 70284392857480
irb(main):006:0> {}.object_id
=> 70284392870480 # different
irb(main):007:0> {}.object_id
=> 70284392904360 # different

多くの場合、すぐに変更される値を初期化するために空のハッシュと配列リテラルが使用されることを理解しています。ただし、これは、代わりに [].freeze.object_id または {}.freeze.object_id を使用してフリーズした場合でも発生します。

これを、環境変数 RUBYOPT が –enable-frozen-string-literal に設定されている場合の String と比較してください。

irb(main):001:0> ""
=> 70284400947400
irb(main):002:0> ""
=> 70284400947400 # same
irb(main):003:0> ""
=> 70284400947400 # same

凍結された文字列リテラルを有効にしなくても、代わりに ““.freeze.object_id を呼び出すと、毎回同じオブジェクト ID が取得されます。ただし、最初の”” リテラルには凍結された中間文字列オブジェクトがまだ割り当てられているのではないかと思われます。呼び出されている。

パフォーマンス重視のコードベースでは (パフォーマンス重視なので、MRI を使用しながらも許容できます (笑))、この回避策を見たことがあります。これは、ハッシュがまたは、配列は可変である必要はありません。

module LessAllocations
  EMPTY_HASH = {}.freeze
  EMPTY_ARRAY = [].freeze

  # String literals are already frozen, use '' instead
  # EMPTY_STRING = ''
end

そこで私の質問は次のとおりです。

解決策

この質問に答えるには、現実世界のメモリ使用量の調査を行う必要がありますが、それは可能性が低いと思います。ちょっとした実験をしてみるといいでしょう…

class Array
  EMPTY_ARRAY = [].freeze
  
  def freeze
    empty? ? EMPTY_ARRAY : super
  end
end

空のオブジェクトは非常に小さいです。プログラムがメモリを使用している他のすべてのものと比較して、大量のメモリを使用することは、特殊なケースです。

そのため、空のハッシュや配列にコピーオンライトを追加すると、速度が低下する可能性があります。

冷凍するということは、空のままになることが事前にわかっていることを意味しますが、これは非常にまれです。既知の空のハッシュと配列が多すぎるため、パフォーマンスの問題が発生するのは、特殊なケースです。継続的な回避策は問題ないようです。