web-dev-qa-db-ja.com

Javaパフォーマンスを大幅に向上させる方法は?

LMAXのチームは、彼らがどのようにして実行できたかについてプレゼンテーションを行っています 1ミリ秒未満のレイテンシで100k TPS 。彼らは a blogtechnical paper(PDF) および source code 自体でそのプレゼンテーションをバックアップしています。

最近 Martin FowlerLMAXアーキテクチャに関する優れた論文 を公開し、毎秒600万の注文を処理できるようになり、チームが実行するいくつかのステップを強調していますパフォーマンスがさらに1桁上がりました。

これまで、ビジネスロジックプロセッサの速度の鍵は、すべてをメモリ内で順番に実行することであると説明しました。これを行うだけで(実際に何もばかげたことはありません)、開発者は10K TPSを処理できるコードを作成できます。

次に、優れたコードの単純な要素に集中すると、これを100K TPSの範囲に引き上げることができることがわかりました。これには、適切に因数分解されたコードと小さなメソッドが必要です。基本的にこれにより、Hotspotは最適化のより良い仕事を行うことができ、実行中のコードをキャッシュする際にCPUがより効率的になります。

別の桁に上がるには、もう少し賢さが必要でした。 LMAXチームがそこに到達するのに役立つとわかった点がいくつかあります。 1つは、Javaコレクションのカスタム実装を書くことでした。これらは、キャッシュフレンドリーでガベージに注意するように設計されています。

その最高レベルのパフォーマンスに到達するためのもう1つの手法は、パフォーマンステストに注意を向けることです。私は人々がパフォーマンスを改善するためのテクニックについて多くのことを話していることに長い間気づきましたが、本当に違いを生むのはそれをテストすることです

ファウラーは発見されたものはいくつかあると述べたが、彼はカップルについてのみ言及した。

そのようなレベルのパフォーマンスを達成するのに役立つ他のアーキテクチャ、ライブラリ、テクニック、または「もの」はありますか?

23
Dakotah North

高性能トランザクション処理にはあらゆる種類のテクニックがあり、Fowlerの記事にあるテクニックは、最先端の多くの技術の1つにすぎません。誰かの状況に適用できるかどうかに関係なく、一連のテクニックをリストするのではなく、基本的な原則と、LMAXがそれらの多くに対処する方法について説明する方がよいと思います。

大規模なトランザクション処理システムの場合、次のすべてを可能な限り実行する必要があります。

  1. 最も遅いストレージ層で費やされる時間を最小限に抑えます。あなたが持っている最新のサーバーで最も速いものから最も遅いものへ:CPU/L1-> L2-> L3-> RAM->ディスク/ LAN-> WAN。最速の最新の磁気ディスクからのジャンプ最も遅いRAMはsequentialアクセスで1000倍以上です;ランダムアクセスはさらに悪いです。

  2. 費やす時間を最小化または排除します待機中。これは、可能な限り少ない状態を共有することを意味します。また、状態mustが共有される場合、可能な限り明示的なロックを回避します。

  3. ワークロードを分散します。 CPUは過去数年でそれほど高速化していませんが、haveは小さくなり、サーバーでは8コアがかなり一般的です。さらに、Googleのアプローチである複数のマシンに作業を分散することもできます。これの素晴らしい点は、I/Oを含めてスケールアップすることですすべて

Fowler氏によると、LMAXはこれらのそれぞれに対して次のアプローチをとっています。

  1. allの状態でメモリにall状態を保持します。ほとんどのデータベースエンジンは実際にこれを行いますifデータベース全体がメモリに収まりますが、偶然に何かを残したくはありません。リアルタイム取引プラットフォームで理解できます。大量のリスクを追加することなくこれを実現するには、軽量のバックアップおよびフェイルオーバーインフラストラクチャを数多く構築する必要がありました。

  2. 入力イベントのストリームには、ロックフリーキュー(「破壊者」)を使用します。従来の耐久性のある メッセージキュー とは対照的notロックフリーであり、実際には通常、非常に遅くなる 分散トランザクション を伴います。

  3. あまりない。 LMAXは、ワークロードが相互依存していることに基づいて、これをバスの下にスローします。 1つの結果が他のパラメータを変更します。これはcriticalの警告であり、Fowlerが明示的に呼び出すものです。 someフェイルオーバー機能を提供するために同時実行を使用しますが、すべてのビジネスロジックは単一スレッドで処理されます。

LMAXはnotが大規模OLTPへの唯一のアプローチです。そして、それ自体は非常に優れていますが、notそのレベルのパフォーマンスを引き出すためには、最先端の手法を使用する必要があります。

上記のすべての原則のうち、ハードウェアが安価であるため、#3がおそらく最も重要で最も効果的です。ワークロードを6ダースのコアと数ダースのマシンに適切に分割できる場合、空は従来の Parallel Computing 技術の限界です。大量のメッセージキューとラウンドロビンディストリビューターだけで、どれほどのスループットを引き出すことができるかに驚かれることでしょう。それは明らかにLMAXほど効率的ではありません-実際にはそれほど接近していません-しかし、スループット、待ち時間、および費用対効果は別の問題であり、ここでは特にスループットについて話しています。

LMAXが行うのと同じ種類の特別なニーズがある場合、特に、急いで設計を選択するのではなく、ビジネスの現実に対応する共有状態の場合は、それらのコンポーネントを試してみることをお勧めします。それ以外の場合は、これらの要件に適しています。ただし、単に高いスケーラビリティについて話している場合は、分散システムについてさらに調査することをお勧めします。分散システムは、現在ほとんどの組織で使用されている標準的なアプローチです(Hadoopおよび関連プロジェクト、ESBおよび関連アーキテクチャ、Cowlers、Fowlerも言及など)。

SSDもまた、画期的なものになるでしょう。間違いなく、彼らはすでにそうです。これで、RAMへのアクセス時間が同等の永続的なストレージを利用できます。サーバーグレードのSSDは依然として非常に高価ですが、採用率が高まると、最終的に価格が下がるようになります。 広範囲にわたって調査されています そして結果はかなり心が揺らぎ、時間の経過とともに良くなるだけなので、「メモリにすべてを保持する」概念全体は、以前よりも重要度が低くなっています。したがって、もう一度、可能な限り並行性に焦点を当てようと思います。

21
Aaronaught

これから学ぶ最大の教訓は、基本から始める必要があるということです。

  • 優れたアルゴリズム、適切なデータ構造、および「本当に愚かな」ことは何もしない
  • よく分解されたコード
  • 性能試験

パフォーマンステストでは、コードをプロファイリングしてボトルネックを見つけ、1つずつ修正します。

「1つずつ修正する」部分にすぐにジャンプする人が多すぎます。彼らは「Javaコレクションのカスタム実装」」を書くのに多くの時間を費やしています。なぜなら、彼らのシステムが遅い理由の全体はキャッシュミスが原因であると知っているからです。それが原因となるかもしれませんが、しかし、そのような低レベルのコードを微調整するためにすぐにジャンプすると、LinkedListを使用する必要があるときにArrayListを使用するという大きな問題を見落とす可能性があります。または、システムが遅い本当の理由は、ORMが遅延しているためです。 -エンティティの子をロードし、リクエストごとにデータベースへの400回の個別のトリップを実行します。

10
Adam Jaskiewicz

LMAXコードについては十分に説明できないと思うので、特にコメントしませんが、パフォーマンスを大幅に向上させることができた例をいくつか紹介します。

いつものように、これらは問題があり、パフォーマンスを向上させる必要があることがわかったときに適用する必要があるテクニックです-そうでない場合、時期尚早の最適化を行っている可能性があります。

  • 正しいデータ構造を使用し、必要に応じてカスタムデータ構造を作成します-正しいデータ構造設計は、マイクロ最適化から得られる改善を小さくします。これが最初。アルゴリズムがパフォーマンスに大きく依存している場合O(1)ランダムアクセス読み取り)、これをサポートするデータ構造があることを確認してください!これを正しく行うには、いくつかのフープにジャンプする価値があります。配列でデータを表す方法を見つけ、非常に高速にO(1)インデックス付き読み取りを利用します。
  • CPUはメモリアクセスよりも高速です-メモリが存在しない場合、1つのランダムメモリを読み取るのにかかる時間でかなりの計算を実行できますL1/L2キャッシュ。メモリの読み取りを節約できる場合は、通常、計算を行う価値があります。
  • finalでJITコンパイラを支援-フィールド、メソッド、クラスをfinalにすることで、JITコンパイラを実際に支援する特定の最適化が可能になります。具体的な例:

    • コンパイラーは、最終クラスにサブクラスがないと想定できるため、仮想メソッド呼び出しを静的メソッド呼び出しに変換できます
    • コンパイラーは、特にコンパイル時に計算できる計算で定数が使用される場合、静的最終フィールドをナイスパフォーマンス向上のための定数として扱うことができます。
    • Javaオブジェクトを含むフィールドがfinalとして初期化される場合、オプティマイザはnullチェックと仮想メソッドディスパッチの両方を排除できます。
  • コレクションクラスを配列で置き換える-これにより、コードが読みにくくなり、保守が難しくなりますが、間接的な層がなくなり、利点が得られるため、ほとんどの場合は高速になりますたくさんのニースのアレイアクセス最適化。通常、ボトルネックとして特定した後の内部ループ/パフォーマンスに敏感なコードでは良い考えですが、読みやすくするためにそれ以外は避けてください。

  • 可能な限りプリミティブを使用する-プリミティブは、オブジェクトベースの同等のものよりも根本的に高速です。特に、ボクシングは大量のオーバーヘッドを追加し、厄介なGCの一時停止を引き起こす可能性があります。パフォーマンス/レイテンシを気にする場合は、プリミティブをボックス化しないでください。

  • 最小限の低レベルのロック-ロックは低レベルでは非常に高価です。完全なロックを回避する方法、または大まかなレベルでロックする方法を見つけてください。大規模なデータブロックに対して頻繁にロックする必要がなく、ロックや同時実行の問題を心配することなく低レベルのコードを実行できます。

  • メモリの割り当てを避けます-JVMガベージコレクションは非常に効率的であるため、実際には全体的に速度が低下する可能性がありますが、極端に低くしようとする場合は非常に役立ちます待ち時間があり、GCの一時停止を最小限に抑える必要があります。割り当てを回避するために使用できる特別なデータ構造があります。特に http://javolution.org/ ライブラリは、これらに優れており、注目に値します。
7
mikera

Aaronaughtからの優れた回答ですでに述べられている以外に そのようなコードは、開発、理解、デバッグが非常に難しい場合があることに注意したいと思います。 LMAXブログ で言及されている彼らの一人として、「非常に効率的ですが...失敗するのは非常に簡単です...」.

  • 従来のクエリとロックに慣れている開発者にとって、新しいアプローチのコーディングは、野生の馬に乗るように感じるかもしれません。 LMAXテクニカルペーパーで言及されている Phaser を実験したとき、少なくともそれは私自身の経験でした。その意味で、私はこのアプローチはロックの競合を開発者の脳の競合と引き換えに言うと思います。

上記を踏まえると、Disruptorや類似のアプローチを選択する方が、ソリューションを維持するのに十分な開発リソースを確実に確保できると思います。

全体として、Disruptorアプローチは私にとって非常に有望に見えます。たとえば上記の理由で会社がそれを利用する余裕がない場合でも、それを研究するために何らかの労力を「投資」するよう経営陣に説得することを検討してください(そして [〜#〜] seda [〜#〜] 一般的に) -そうしないと、ある日、顧客が、4倍、8倍などの少ないサーバーを必要とする、より競争力のあるソリューションを支持する可能性があるためです。

4
gnat