諸行無常

IT色々お勉強中のブログ

マジックコメントでRubyの最適化をする Ruby Optimization with One Magic Comment

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

  1. mutateを行う場合は、String.newを使って ""の代わりにStringを割り当てます。

  2. 魔法のコメントはRuby 2.3以降でしか動作しませんが、Ruby 2.2は1ヶ月でEOLなので、パフォーマンスチューニングをやめるのは間違いないと思います。

RubyGemsの複数の脆弱性 Multiple vulnerabilities in RubyGems

Multiple vulnerabilities in RubyGems

Rubyに入っているRubyGemsには、複数の脆弱性が存在します。それはRubyGemsの公式ブログで報告されています。

詳細 次の脆弱性が報告されています。

  • ルート以外のシンボリックリンクされたベースディレクトリに書き込むときに、pathトラバーサルを防ぐ。

  • 安全でないObject逆シリアル化の可能性を修正します。gem所有者の脆弱性

  • tarヘッダーの8進フィールドを厳密に解釈します。

  • パッケージに重複ファイルが存在する場合にセキュリティエラーを発生させます。

  • specホームページ属性にURL検証を強制します。

  • gemサーバー経由で表示された場合、ホームページ属性のXSS脆弱性を緩和します。

  • インストール時のpathトラバーサルの問題を防止します。

Rubyユーザーは、次のいずれかの回避策をできるだけ早く実行することを強く推奨します。

影響を受けるバージョン Ruby 2.2シリーズ

  • :2.2.9以前 Ruby 2.3シリーズ

  • :2.3.6以前 Ruby 2.4シリーズ

  • :2.4.3以前 Ruby 2.5シリーズ

  • :2.5.0以前 prior to trunk revision62422以前

回避策 RubyGems 2.7.6以降には脆弱性に対する修正が含まれているため、RubyGemsを最新バージョンにアップグレードしてください。

gem update --system

RubyGemsをアップグレードできない場合は、回避策として次のパッチを適用できます。

https://bugs.ruby-lang.org/attachments/download/7030/rubygems-276-for-ruby22.patch

https://bugs.ruby-lang.org/attachments/download/7029/rubygems-276-for-ruby23.patch

https://bugs.ruby-lang.org/attachments/download/7028/rubygems-276-for-ruby24.patch

https://bugs.ruby-lang.org/attachments/download/7027/rubygems-276-for-ruby25.patch

How Fast is Ruby 2.5.0?

engineering.appfolio.com

11月には、Ruby 2.5.0プレビュー1の速度結果を掲載しました。 Ruby 2.4よりはるかに高速でしたが、これは少し失望しました。 しかし、1つの非常に重要なパフォーマンスパッチが終了する前にリリースしたため、最終的な速度に大きな違いが生じました

どれぐらいの差か?さぁ見てみましょう!

グラフを見たいだけです、同じ方法だと私は言い切れます。 これは素晴らしいスタートです.Rails Ruby Benchのトータルタイムです。 これは、談話(Rails)要求の混在を大規模な並行サーバー経由でプッシュするのにかかる時間を測定します。

悪くない。数字とパーセントの表のように見えるのは何ですか?

Percentile Ruby 2.4.3 Ruby 2.5.0 % Faster

0% 29.17 26.99 7.5%

10% 32.25 30.73 4.7%

50% 33.98 32.39 4.7%

90% 35.15 33.37 5.1%

100% 36.77 35.62 3.1%

ここで興味深いのは、より高い(より遅い)runの方がスピードが遅くなっていることです。 ほぼすべてのスピードの違いがあるパフォーマンスパッチの珍しい性質のため、ほぼ確実です。 それはRubyバイトコード操作ごとに多かれ少なかれ一定のオーバーヘッドでした。より遅い実行にはそれぞれが長い(かなり可能性がある)命令がある場合は、パフォーマンスが低下すると予想されます。おおよそあなたがここに見るものです。

Ruby 2.5.0の方がずっと速いのですが、 「実行時間あたりのパーセンタイルではなく、スループットに答えようと思っています。だからスループットを見てみましょう:

Measurement | Ruby 2.4.3 | Ruby 2.5.0 | % Faster|

Mean Throughput | 170.6 | 179.3 | 5.1%|

Median Throughput| 171.0 | 179.6 | 5.0%|

どれくらい速くなる?大規模な並行Railsサーバーではスループットが5%向上します。友達に教えて!

それよりも速くすることはできますか? 確かに。小さくて速い操作で、Railsの要求が最大7.5%向上しました。一部のベンチマークではKoichiが最大12%速くなっています。

しかし簡単な答えのために、 "それは私のRailsアプリケーションをより速くするでしょうか?"はい。約5%速くなります。何かを壊すようなことはないかなり静かなアップグレードのために悪いことではありません。コードをスピードアップし、いくつかの素晴らしい機能を追加するだけです!

rubocopとの奮闘記録 途中

Use a guard clause instead of wrapping the code inside a conditional expression. 条件分岐のネストが深くなるのはダメ

bad

def hoge
   if boge? || kuzu?
    'sine'
   end
end

good

def hoge
    'sine' if boge? || kuzu?
end

Don't use parentheses around the condition of an if.

ifにカッコはいらんわ!

Favor a normal if-statement over a modifier clause in a multiline statement.

def create_transfer_request
      ContentDepositorChangeEventJob.perform_later(self,
                                                   'sample') if on_behalf_of.present?
    end

def create_transfer_request
      if on_behalf_of.present?
        ContentDepositorChangeEventJob.perform_later(self,
                                                     'sample')
      end
    end

Style/IfInsideElse: Style/IfInsideElse:

elseの中にif入れんならelsifにしろ

if hoge?
  action_b
else
  if hage?
    action_b
  else
    action_c
  end

if hoge?
  action_b
elsif hage?
    action_b
  else
    action_c
  end

Class: RuboCop::Cop::Style::ConditionalAssignment

is_push ? self.hoge = true : self.hoge = false

↓の方がイケてるっぽい
self.hoge = if is_push
                                     true
                                  else
                                     false
                                   end

Ruby on RailsによるJSON APIのシリアライズの高速化

英訳

Ruby on Railsアプリケーション向けの高速 JSON API gemのオープンソース・リリースを発表します。

  • Introduction

高速JSONAPIは、Active Model Serializer(AMS)が提供する主要な機能をすべて提供するとともに、 AMSよりも25倍高速というベンチマーク要件を満足しており、 スピードとパフォーマンスに重点を置いています。 このgemは、ルールとしてパフォーマンステストも実施します。 AMSは偉大なgemであり、fast_jsonapiは宣言の構文と機能について言及しています。 しかし、モデルに1つ以上のリレーションシップがある場合、AMSは遅くなり始めます。 これらのモデルでは複合ドキュメント、AKAサイドローディングはAMSをさらに遅くします。 UI上で無限のスクロールが必要になり、AMSの遅さがユーザーに見え始めます。

  • シリアル化を最適化するのはなぜですか?

JSON APIのシリアライゼーションは、よく実装された多くのRails APIのうち最も遅い部分の1つです。

AMSが提供する主要な機能をすべて高速で提供してみませんか?

  • 特徴:

    • アクティブモデルシリアライザに似た宣言構文
    • belongs_to、has_many、およびhas_oneのサポート
    • 複合文書のサポート(付属)
    • 複合ドキュメントの最適化されたシリアル化
    • キャッシング
    • Skylightインテグレーションによる計測(オプション)
  • Fast JSONAPIを使用してシリアライザを作成するにはどうすればよいですか?

Active Modelシリアライザは、シリアライザを宣言するのに馴染みのある方法を使います。 fast_jsonapiの宣言構文はAMSに似ています。

class MovieSerializer
   include FastJsonapi::ObjectSerializer
   attributes :name, :year
   has_many :actors
   belongs_to :owner, record_type: :user
   belongs_to :movie_type
end
  • アクティブモデルシリアライザと比較するとどれくらい早いですか?

性能テストでは、AMSと比較して25〜40倍の速度向上が見られ、かなり複雑なモデルでもシリアライズ時間は無視できます。 シリアライズされたレコードの数が増えると、パフォーマンスが向上します。

私たちを信じてください?ベンチマークテストは自分で実行できます。 readmeを参照してください。

  • 依存

JSON APIはanti-bikesheddingです。

  • Future Work

我々は、gemに多くの機能を追加する予定です。提案、改善、修正、追加テストを歓迎します。