Ruby
 Computer >> コンピューター >  >> プログラミング >> Ruby

Railsのパフォーマンス:適切な選択を行うのはいつですか?

プログラミング用語では、キャッシングとは、将来すばやく取得できるように1つまたは複数の値を格納することを指します。通常、これは、何らかの理由で計算が遅い値を使用して行います。たとえば、取得するために外部APIをヒットする必要がある場合や、生成するために多くの数値計算が必要になる場合があります。

キャッシュされた値は、多くの場合、memcachedやRedisなどの別のサーバーに保存されます。それらはディスクまたはRAMに保存できます。コードでは、高価な関数を何度も呼び出さないように、変数内にデータを「キャッシュ」することがよくあります。

data = some_calculation()
a(data)
b(data)

あなたが得るすべての速度のトレードオフは、あなたが古いデータを使用しているということです。キャッシュされたデータが「古く」なり、正確でなくなった場合はどうなりますか?キャッシュを「無効化」するには、キャッシュをクリアする必要があります。

キャッシングに対する議論

古いことわざにあるように、コンピュータサイエンスには2つの難しい問題しかありません:1。ネーミング2。キャッシュの無効化3。オフバイワンエラー

キャッシュの無効化が非常に難しいのはなぜですか?キャッシュされた値は、その性質上、実際の値を「隠します」。 '実際の'値が変更されるたびに、あなた(はい、あなた、プログラマー)は、キャッシュが更新されるように、キャッシュを'無効化'することを忘れないでください。

「単語数」ウィジェットをテキストエディタに追加するとします。ユーザーが入力するたびに単語数を更新する必要があります。最も簡単なアプローチは、キーストロークごとに単語を再カウントすることですが、これは遅すぎます。別のアプローチがあります:

  1. ファイルを読み込むときに単語を数えます。
  2. この単語数を変数に保存します(または「キャッシュします」)。
  3. 変数の内容を画面に表示します。

この実装ははるかに高速ですが、キャッシュされた「単語数」は入力しても変化しません。そのためには、単語数が変化すると予想される場合は常に、キャッシュを「無効化」する必要があります。

これで、キーストロークが行われると、単語(つまり、スペース)を検出し、単語カウンターをインクリメントします。もちろん、ユーザーが単語を削除するときにもデクリメントします。簡単。終わり。次のチケット。

...しかし、待ってください。ユーザーがクリップボードにテキストを切り取ったときに単語数を更新することを覚えていますか?彼らがテキストを貼り付けるときはどうですか?スペルチェッカーがタイプミスを2つの単語に分割した場合はどうでしょうか。

ここでの問題は、値を更新しないことです。これは非常に簡単です。問題は、すべての場所で更新することを忘れないでください。 。これらの更新の1つだけが欠落していると、キャッシュの無効化の問題が発生します。つまり、ユーザーに古い値が表示されます。

これを念頭に置いて、キャッシュを追加すると、技術的な複雑さと潜在的なバグの原因がもたらされることがわかります。もちろん、これらの問題は解決できますが、解決策としてキャッシングにジャンプする前に覚えておく必要があります。

キャッシュなしの速度

キャッシュをテーブルから外すと、アプリケーションの高速化は、パフォーマンスのボトルネック(システムの速度が遅くなる可能性がある)を特定して修正することがすべてです。それらを3つの全体的なカテゴリにグループ化できます。

  1. データベースクエリ(多すぎるか遅すぎる)
  2. レンダリングを表示
  3. アプリケーションコード(例:大量の計算の実行)

パフォーマンスに取り組む場合、前進するために知っておく必要のある2つの手法があります。プロファイリングとベンチマークです。

プロファイリング

プロファイリングは、場所を知る方法です。 問題はアプリにあります:テンプレートのレンダリングが遅いため、このページは遅いですか?または、データベースに100万回アクセスしているため、速度が遅いですか?

Ruby on Railsの場合は、rack-mini-profilerをお勧めします。これにより、アプリのエッジに小さなウィジェットが追加されます。実行されたデータベースクエリの数、実行にかかった時間、レンダリングされたパーシャルの数など、表示しているページのレンダリングにかかった時間の概要がわかります。

本番用(プロのヒント:rack-mini-profiler 本番環境でうまく機能します。管理者や開発者などの特定のユーザーにのみ表示されることを確認してください)、ページのパフォーマンスを監視するSkylight、New Relic、Scoutなどのオンラインサービスがあります。

一般的に引用されるターゲット<= 100ms これよりも小さいものは、実際のインターネットの使用状況ではユーザーが検出するのが難しいため、ページのレンダリングに最適です。あなたの目標は多くの要因によって異なります。ある時点で、パフォーマンスがひどいレガシーアプリケーションで作業しているときに、<=1秒をターゲットにしました。これは素晴らしいことではありませんが、開始したときよりもはるかに優れています。

ベンチマーク

どこを見つけたら 問題は、ベンチマークを使用して、最適化がパフォーマンスにどのような影響を及ぼしたか(ある場合)を確認できることです。個人的には、この種の作業にbenchmark-ips gemを使用するのが好きです。これにより、コードがもたらした違いを人間が簡単に確認できるようになります。

簡単な例として、文字列の連結と文字列の補間を比較します。

require 'benchmark/ips'

@a = "abc"
@b = "def"
Benchmark.ips do |x|
  x.report("Concatenation") { @a + @b }
  x.report("Interpolation") { "#{@a}#{@b}" }
  x.compare!
end

と結果:

Warming up --------------------------------------
       Concatenation   316.022k i/100ms
       Interpolation   282.422k i/100ms
Calculating -------------------------------------
       Concatenation     10.353M (± 7.4%) i/s -     51.512M in   5.016567s
       Interpolation      6.615M (± 6.8%) i/s -     33.043M in   5.023636s

Comparison:
       Concatenation: 10112435.3 i/s
       Interpolation:  6721867.3 i/s - 1.50x  slower

これにより、人間が読める形式の優れた結果が得られ、補間は連結よりも1.5倍遅くなります(少なくとも小さな文字列の場合)。このため、改善しようとしているメソッドをコピーして、新しい名前を付けることもお勧めします。次に、簡単な比較を実行して、パフォーマンスが向上しているかどうかを確認できます。

パフォーマンスの問題の修正

この時点で、アプリのどの部分が遅いかがわかります。改善が発生した場合にそれを測定するためのベンチマークが用意されています。ここで、パフォーマンスを最適化する実際の作業を行う必要があります。選択する手法は、データベース、ビュー、またはアプリケーションのどこに問題があるかによって異なります。

データベースのパフォーマンス

データベース関連のパフォーマンスの問題については、注意すべき点がいくつかあります。まず、恐ろしい「N+1クエリ」を避けます。このような状況は、ビューでコレクションをレンダリングするときによく発生します。たとえば、10個のブログ投稿を持つユーザーがいて、そのユーザーとそのすべての投稿を表示したいとします。素朴なファーストカットは次のようなものかもしれません:

# Controller
def show
  @user = User.find(params[:id])
end
# View
Name: <%= @user.name %>
Posts:
  <%= @user.posts each do |post| %>
    <div>Title: <%= post.title %></div>
  <% end %>

上記のアプローチでは、ユーザーを取得し(1クエリ)、個々の投稿ごとにクエリを実行し(N =10クエリ)、合計11(またはN + 1)になります。幸い、Railsは、.includes(:post)を追加することで、この問題の簡単な解決策を提供します。 ActiveRecordクエリに。したがって、上記の例では、コントローラーコードを次のように変更するだけです。

def show
  @user = User.includes(:post).find(params[:id])
end

次に、ユーザーを取得します 1つのデータベースクエリ内のすべての彼または投稿。

もう1つ探す必要があるのは、計算をデータベースにプッシュできる場所です。これは通常、アプリケーションで同じ操作を実行するよりも高速です。これの一般的な形式は、次のような集計です。

total = Model.all.map(&:somefield).sum

これはデータベースからすべてのレコードを取得していますが、実際の値の合計はRubyで行われます。データベースに次のように計算を実行させることで、これを高速化できます。

total = Model.sum(:somefield)

おそらく、2つの列を乗算するなど、より複雑なものが必要になります。

total = Model.sum('columna * columnb')

一般的なデータベースは、このような基本的な算術演算と、合計や平均などの一般的な集計もサポートしているため、map(...).sumに注意してください。 コードベースでの呼び出し。

パフォーマンスの表示

テンプレート関連のパフォーマンスの問題は、解決策としてキャッシュに役立つと思いますが、最初に除外したいいくつかの簡単な成果がまだあります。

一般的なページの読み込み時間については、JavascriptまたはCSSライブラリ(少なくとも本番サーバー上)に縮小されたソースを使用していることを確認できます。

また、多数のパーシャルが含まれていることに注意してください。 _widget.html.erbの場合 テンプレートの処理には1ミリ秒かかりますが、ページに100個のウィジェットがある場合、それはすでに100ミリ秒です。 1つの解決策は、UIを再検討することです。一度に100個のウィジェットを画面に表示することは、通常、優れたユーザーエクスペリエンスではありません。何らかの形のページ付け、またはおそらくさらに大幅なUI/UXのオーバーホールの使用を検討することをお勧めします。

アプリケーションコードのパフォーマンス

パフォーマンスの問題が、ビューまたはデータベースレイヤーではなく、アプリケーションコード自体(つまり、データの操作)にある場合は、いくつかのオプションがあります。 1つは、上記のようにクエリとして、またはおそらく風光明媚な宝石のようなデータベースビューとして、作業の少なくとも一部をデータベースにプッシュできるかどうかを確認することです。

もう1つのオプションは、「重労働」をバックグラウンドジョブに移動することですが、値が非同期で計算されるようになるという事実を処理するためにUIを変更する必要がある場合があります。

まだキャッシュが必要です。今何?

これらすべてを完了したので、おそらく、キャッシングが必要なソリューションであると判断したかもしれません。それで、あなたは何をすべきですか?これは、Ruby on Rails内で利用可能なさまざまな形式のキャッシングをカバーする一連の記事の最初のものであるため、ご期待ください。


  1. Railsアプリのパフォーマンスを理解するための新しい方法

    Railsアプリは遅いですか? 単純なビューであるはずのものをロードするのに数秒かかる場合、掘り下げる価値のある問題があります。 データベース呼び出しが多すぎるか、メソッドが遅い可能性があります。あるいは、誰かがあなたのコードに入れて忘れてしまったスピードアップループかもしれません。 アプリの速度を低下させている原因を見つけるのに役立つツールがたくさんあります。数週間前、私はrbtraceについて話しました 。 NewRelicのrpm gemは、アプリの高速化にも役立ちました。 しかし、パフォーマンスの問題を調査するための私のお気に入りのツールは、さらに多くのことを行います。 箱か

  2. Android をオーバークロックして適切な方法でパフォーマンスを向上させる

    新しい更新された Android スマートフォンは、新しい更新と機能を備えた市場に常に登場しています。その結果、より多くのゲームやアプリがそれらをサポートするために定期的に更新されているため、より多くの電力を消費し、古いスマートフォンの速度が低下しています.あまりにも多くのアプリを開くと、スマートフォンで遅延が発生した可能性があります。誰もが時々新しいスマートフォンを購入する余裕はありません。 Android デバイスのパフォーマンスを向上できることを知ったらどうしますか?あなたはそれがどのように可能か尋ねますか?しかし、オーバークロックと呼ばれる方法で可能です。オーバークロックについて詳しく