Ruby Optimization with One Magic Comment
ソフトウェアのパフォーマンスの最適化は簡単です:少ないことをする方法を見つける。
Rubyは遅いという評判があります。その印象は10年前からですが、主要な犯罪者の1人はガベージコレクタです。
これは、gabageを少なくしてRubyのスピードを上げることができますか?もちろん!
文字列の入門
Rubyは、すべての文字列が変更可能であるという不幸なデフォルト文法を持っています:
string = ""
string << "mike"
これは2つの文字列 ""と "mike"を割り当てます。最初の空文字列は、「mike」を含むように変更されます。
しかし、文字列の変異は非常にまれであり、より一般的なのは次のようなものです:
HASH = {
"mike": 123
}
def getmike
HASH["mike"] # unnecessary garbage here!
end
getmikeを呼び出すたびに "mike"の新しいコピーが割り当てられ、すぐにガベージとして破棄されますが、
RubyはStringをHash#[]内で変更されるメソッドの引数として扱うだけなので必要です。とても無駄です!
Freeze!
Rubyは、割り当てを最小限に抑えるために、何年も前にFreezeの概念を導入しました。オブジェクトのフリーズを呼び出すと、
Rubyはそれを不変として扱うように指示します。 Rubyは "mike"を定数として扱うことができます:
def getmike
HASH["mike".freeze]
end
Magic Comments!
Ruby 2.3では、非常に良いオプションが導入されました。各Rubyファイルは文字列を不変として選択できます。
つまり、ファイル内のすべての文字列が自動的にフリーズし、ファイルの先頭に簡単な魔法のコメントが付きます。
これは、 "mike"に追加のStringを割り当てません。
# frozen_string_literal: true
HASH = {
"mike": 123
}
def getmike
HASH["mike"]
end
実世界
数年前、ガーベジコレクタへの影響を最小限に抑え、パフォーマンスを最大化するために、Sidekiqに多くのフリーズコールを追加しました。
先週、これらの呼び出しをすべて削除し、すべてのRubyファイルにfrozen_string_literalコメントを追加しました。
この効果を確認するために、Sidekiqのベンチマークスクリプトを使ってGC.disableを追加し、RSSが成長するのを見て、
frozen_string_literalの実験を行った。
Rubyでは、フラグ付きの機能を有効または無効にすることができます。
無効
$ RUBYOPT=--disable=frozen-string-literal bundle exec bin/sidekiqload
Created 30000 jobs
RSS: 105852 Pending: 25749
RSS: 178880 Pending: 21514
RSS: 252804 Pending: 17306
RSS: 326824 Pending: 12987
RSS: 399268 Pending: 8810
RSS: 472620 Pending: 4618
RSS: 547968 Pending: 319
RSS: 553568 Pending: 0
Done
有効
$ RUBYOPT=--enable=frozen-string-literal bundle exec bin/sidekiqload
Created 30000 jobs
RSS: 105824 Pending: 25687
RSS: 174948 Pending: 21700
RSS: 245448 Pending: 17669
RSS: 316848 Pending: 13559
RSS: 388544 Pending: 9447
RSS: 456704 Pending: 5288
RSS: 450552 Pending: 1160
RSS: 457536 Pending: 0
Done
rozen_string_literalは、生成されたガベージを〜100MBまたは〜20%削減します! 1行のコメントを追加してパフォーマンスを向上させます。
結論
gemの作者:# frozen_string_literal: true:gemのすべてのRubyファイルのトップに追加してください。
これは、文字列の突然変異を使用しない限り、すべてのユーザーに無料のパフォーマンス向上をもたらします。
Notes
mutateを行う場合は、String.newを使って ""の代わりにStringを割り当てます。
魔法のコメントはRuby 2.3以降でしか動作しませんが、Ruby 2.2は1ヶ月でEOLなので、パフォーマンスチューニングをやめるのは間違いないと思います。