tl;dr
Hash キーへのアクセスは String よりも Symbol の方が速いことがあるよというのを本で
読んだけど、その速さの違いはどれくらいなのか、又、歴代の Ruby バージョンでどの位の
違いがあるのかを比較することにしました。
既に
試されている方がいらっしゃいますが、自分でも試してみらんといかんばいということで、
そちらの記事を参考にしつつ試してみたいと思います。
- HashキーのStringアクセスとSymbolアクセスのパフォーマンス比較 – Hack Your Design!
- Rubyのベンチマークを計測するのにbenchmark-ipsが便利そうだった | 69log
- Unraveling String Key Performance in Ruby 2.2
上記の記事、とても参考になりました。ありがとうございます。
環境
OS
$ cat /etc/lsb-release DISTRIB_ID=Ubuntu DISTRIB_RELEASE=14.04 DISTRIB_CODENAME=trusty DISTRIB_DESCRIPTION="Ubuntu 14.04.2 LTS"
Ruby バージョン
rbenv を利用して以下のバージョンを用意。
$ rbenv versions * system (set by /home/vagrant/git/ruby-tutorial/.ruby-version) 1.9.3-p551 2.1.7 2.2.3
ベンチマークツール
詳細な使い方については README をご一読下さい。
検証用コード
以下のようなコードを用意しました。
require 'benchmark/ips' HASH_01 = {'foo' => 'bar'} HASH_02 = {:foo => 'bar'} Benchmark.ips do |x| x.report('String') { HASH_01['foo'] } x.report('Symbol') { HASH_02[:foo] } x.compare! end
ハッシュの入れ物自体は定数、特に大きな意味は無いのです。
試す
実行方法
以下のように rbenv で Ruby のバージョンを切り替えながら、同バージョンで 3 回実施しました。
$ rbenv local 1.9.3-p551 $ ruby benchmark.rb Calculating ------------------------------------- String 119.019k i/100ms Symbol 150.413k i/100ms ------------------------------------------------- String 4.741M (± 5.1%) i/s - 23.328M Symbol 8.673M (± 7.6%) i/s - 42.116M Comparison: Symbol: 8672894.5 i/s String: 4740561.3 i/s - 1.83x slower
3 回実施した上で Comparison の結果を整理しました。
Ruby 1.9.3-p551
1 回目 | 2 回目 | 3 回目 | |
---|---|---|---|
Symbol(i/s) | 8672894.5 | 8603563.7 | 8660111.2 |
String(i/s) | 5418971.5 | 5427069.0 | 5330578.7 |
差 | 1.84xslower | 1.89x slower | 1.86x slower |
Ruby 2.1.7
1回目 | 2 回目 | 3 回目 | |
---|---|---|---|
Symbol(i/s) | 9955217.0 | 10242990.0 | 9906651.8 |
String(i/s) | 5418971.5 | 5427069.0 | 5330578.7 |
差 | 1.84x slower | 1.89x slower | 1.86x slower |
Ruby 2.2.3
1 回目 | 2 回目 | 3 回目 | |
---|---|---|---|
Symbol(i/s) | 10276582.7 | 10259725.6 | 10393689.3 |
String(i/s) | 8713234.5 | 8732910.8 | 8633184.5 |
差 | 1.18x slower | 1.17x slower | 1.20x slower |
計測により判ったこと
- Symbol アクセスの方が速いことが判った
- Ruby 1.9.3-p551 と Ruby 2.1.7 では 2 倍弱の差があったが、2.2.3 では 1.2 倍程度に差が縮まっているのが興味深い
- Ruby のバージョンが上がるに連れて全体的に処理性能があがっている
おまけ
なぜ Symbol が速いのか
後で調べて書いてみたいが、以下の記事が参考になりそう。
上記の記事より抜粋したのものを雑に意訳すると…
If you use a string as a Hash key, Ruby needsto evaluate the string and
look at it’s contents(and compute a hash function on that) and compare
the result against the (hashed) values of the keys which are already stored in the Hash.
ハッシュキーとして文字列を利用する場合、まずは文字列の評価を行った上でその内容と
ハッシュキーの比較を行ったうえで結果を返す。
If you use a symbol as a Hash key, it’s implicit that it’s immutable, so
Ruby can basically just do a comparison of the (hash function of the)
object-id against the (hashed) object-ids of keys which are already stored
in the Hash. (much faster)
対して、ハッシュキーとしてシンボルを利用する場合にはハッシュキーのオブジェクト ID
を利用して比較を行う。オブジェクト ID は不変であることから文字列よりも早く検索、
比較して結果を返すことが出来る。
さらに雑に解釈すると…
- String はオブジェクト ID が変わってしまう
- Symbol はオブジェクト ID が不変
ということなので Symbol で指定した方が早く結果を返すことが出来る。
(フワッとしている…汗)
実行ログ
- Ruby 1.9.3-p551
$ rbenv local 1.9.3-p551 $ ruby benchmark.rb Calculating ------------------------------------- String 119.019k i/100ms Symbol 150.413k i/100ms ------------------------------------------------- String 4.741M (± 5.1%) i/s - 23.328M Symbol 8.673M (± 7.6%) i/s - 42.116M Comparison: Symbol: 8672894.5 i/s String: 4740561.3 i/s - 1.83x slower $ ruby benchmark.rb Calculating ------------------------------------- String 120.142k i/100ms Symbol 149.733k i/100ms ------------------------------------------------- String 4.732M (± 3.7%) i/s - 23.428M Symbol 8.604M (± 7.2%) i/s - 41.925M Comparison: Symbol: 8603563.7 i/s String: 4732022.1 i/s - 1.82x slower $ ruby benchmark.rb Calculating ------------------------------------- String 111.907k i/100ms Symbol 149.702k i/100ms ------------------------------------------------- String 4.737M (± 5.0%) i/s - 23.389M Symbol 8.660M (± 6.9%) i/s - 42.216M Comparison: Symbol: 8660111.2 i/s String: 4736707.4 i/s - 1.83x slower
- Ruby 2.1.7
$ rbenv local 2.1.7 $ ruby benchmark.rb Calculating ------------------------------------- String 136.530k i/100ms Symbol 166.095k i/100ms ------------------------------------------------- String 5.419M (± 4.0%) i/s - 26.760M Symbol 9.955M (± 7.5%) i/s - 48.500M Comparison: Symbol: 9955217.0 i/s String: 5418971.5 i/s - 1.84x slower $ ruby benchmark.rb Calculating ------------------------------------- String 145.433k i/100ms Symbol 174.928k i/100ms ------------------------------------------------- String 5.427M (± 3.9%) i/s - 26.905M Symbol 10.243M (± 6.5%) i/s - 50.029M Comparison: Symbol: 10242990.0 i/s String: 5427069.0 i/s - 1.89x slower $ ruby benchmark.rb Calculating ------------------------------------- String 143.528k i/100ms Symbol 169.065k i/100ms ------------------------------------------------- String 5.331M (± 3.7%) i/s - 26.409M Symbol 9.907M (± 6.8%) i/s - 48.353M Comparison: Symbol: 9906651.8 i/s String: 5330578.7 i/s - 1.86x slower
- Ruby 2.2.3
$ rbenv local 2.2.3 $ ruby benchmark.rb Calculating ------------------------------------- String 172.781k i/100ms Symbol 178.983k i/100ms ------------------------------------------------- String 8.713M (± 5.1%) i/s - 42.677M Symbol 10.277M (± 6.5%) i/s - 50.294M Comparison: Symbol: 10276582.7 i/s String: 8713234.5 i/s - 1.18x slower $ ruby benchmark.rb Calculating ------------------------------------- String 180.932k i/100ms Symbol 188.602k i/100ms ------------------------------------------------- String 8.733M (± 5.2%) i/s - 42.881M Symbol 10.260M (± 5.3%) i/s - 50.357M Comparison: Symbol: 10259725.6 i/s String: 8732910.8 i/s - 1.17x slower $ ruby benchmark.rb Calculating ------------------------------------- String 161.864k i/100ms Symbol 169.272k i/100ms ------------------------------------------------- String 8.633M (± 6.5%) i/s - 42.247M Symbol 10.394M (± 5.8%) i/s - 50.612M Comparison: Symbol: 10393689.3 i/s String: 8633184.5 i/s - 1.20x slower