with_indifferent_accessはなぜ文字列キーを使うのか?
前回からの続き。
- ところで、最近のRubyは{abc:123}({:abc => 123}と同等)のようなハッシュ定義も可能になり、間違いなくシンボルキーが多用される時代なのに...
with_indifferent_accessは、なぜ文字列キーを使うのか?
- シンボルは無駄にオブジェクトを生成しない。
- コード中に:fooがいくつあっても、たった一つの:fooオブジェクトを参照するだけ。
irb(main):001:0> :foo.object_id => 538568 irb(main):002:0> :foo.object_id => 538568 irb(main):003:0> :foo.object_id => 538568
-
- オブジェクトIDがすべて538568。同じである。
- 一方、文字列は何度も異なるオブジェクトを生成する。
- コード中に"foo"が出現する度に、異なる"foo"オブジェクトを生成している。
irb(main):004:0> 'foo'.object_id => 70228655214520 irb(main):005:0> 'foo'.object_id => 70228655232480 irb(main):006:0> 'foo'.object_id => 70228655227360
-
- オブジェクトIDの下6桁が、すべて違っている。
- 以上のことからも、シンボルの方が無駄なことをせずに良さそうなのだが、なぜ文字列キーが使われるのか?
- 実は、この無駄のないシンボルの性質が、逆に弱点となる場合もあったのだ。
- シンボルはたった一つのオブジェクトしか生成しない。
- そして、一度生成されたシンボルは、後の再利用のためにメモリ中にずっと残る。
- つまり、GC(ガベージコレクション*1)の対象にならないのだ。
- 一方、文字列はオブジェクトを繰り返し生成する。
- しかし、使われなくなるとGCされ、メモリは解放されるのだ。
- Webアプリケーションのような不特定な外部入力を受け取って、ハッシュに変換して処理する状況では、
- GCされるかどうかの違いによって、シンボルは弱点になってしまうこともあるらしい。
- GCされないということは、多分もう使われないシンボルであっても、rubyプロセスを再起動するまでずっと残り続けることになる。
- シンボルには、無駄にオブジェクトは生成しないけれど、一旦生成されるとずっとメモリを占有し続ける、という性質があったのだ。
- そうは言っても、今どきのサーバーはメモリも潤沢だし、たかがシンボルごときのメモリ占有によって、問題になるような影響は出ないんじゃないか?と思ってしまう。
- 善意のユーザーが想定の範囲内で利用する場合においては、たぶんメモリ不足のような問題は発生しない。
- ところが、悪意のあるユーザーが何らかの攻撃を仕掛けてきたら、目論見どおりメモリ不足になってしまうことがあるらしい。
- そのような悪意のある攻撃に対抗するために、with_indifferent_accessは文字列キーを選択していたのだ。
- indifferentなハッシュは、その内部は文字列キーに統一されている。
- でもアクセスする時には、シンボル・文字列どちらのキーも使える。
irb(main):001:0> require 'active_support/all' => true irb(main):002:0> h={abc:123}.with_indifferent_access => {"abc"=>123} irb(main):003:0> h[:abc] => 123 irb(main):004:0> h[:efg]=456 => 456 irb(main):005:0> h => {"abc"=>123, "efg"=>456}
参考ページ
素晴らしい情報に感謝です!
シンボルの脆弱性
- RubyのHashのキーはシンボルにするべき? - QA@IT
- シンボルキーの脆弱性を悟った。
シンボルの未来
- RubyKaigi2014 速報(3) – Symbol GC | Akatsuki Hackers Lab アカツキハッカーズラボ
- Ruby 2.2.0-preview1 リリース
- GCの対象となるシンボルを作ろうという動きもあるらしい。
*1:不要になったメモリ領域を解放する処理のこと。