paint-brush
ソフトウェアは、最初に殺さない限り繁栄する: 時期尚早な最適化と Java GC の物語@wasteofserver
549 測定値
549 測定値

ソフトウェアは、最初に殺さない限り繁栄する: 時期尚早な最適化と Java GC の物語

Frankie6m2024/04/06
Read on Terminal Reader

長すぎる; 読むには

最適化をやり過ぎず、言語に任せましょう。ストーリーを語ってください。Java のアップグレードにより、無料でパフォーマンスが向上します。常にベンチマークしてください。
featured image - ソフトウェアは、最初に殺さない限り繁栄する: 時期尚早な最適化と Java GC の物語
Frankie HackerNoon profile picture
0-item

LinkedList の方が高速でしょうか? `for each` を `iterator` に置き換えるべきでしょうか? この `ArrayList` は `Array` にすべきでしょうか? この記事は、非常に悪意のある最適化への対応として書かれたもので、私の記憶に永久に刻み込まれています。


Java と、ガベージ コレクターまたはコンテキスト切り替えによる干渉に対処する方法について詳しく説明する前に、まず将来の自分のために、コード記述の基礎について簡単に説明します。


時期尚早な最適化は諸悪の根源です。


これまでにも聞いたことがあると思いますが、時期尚早な最適化は諸悪の根源です。まあ、時々はそうなります。ソフトウェアを作成するときは、次の点を強く信じています。


  1. できるだけ詳細に、物語を書いているかのように意図を語るようにしてください。


  2. 可能な限り最適にする必要があります。つまり、言語の基礎を理解し、それに応じて適用する必要があります。

できるだけ詳しく説明

コードは意図を表す必要があり、その多くはメソッドや変数に名前を付ける方法に関係しています。


 int[10] array1; // bad int[10] numItems; // better int[10] backPackItems; // great

変数名だけで、機能性を推測できます。


numItemsは抽象的ですが、 backPackItems予想される動作について多くのことを伝えます。


あるいは、次のようなメソッドがあるとします。


 List<Countries> visitedCountries() { if(noCountryVisitedYet) return new ArrayList<>(0); } // (...) return listOfVisitedCountries; }

コードに関する限り、これはほぼ問題ないように見えます。


もっと良いものが作れるでしょうか? 絶対できます!


 List<Countries> visitedCountries() { if(noCountryVisitedYet) return Collections.emptyList(); } // (...) return listOfVisitedCountries; }

Collections.emptyList()を読むほうnew ArrayList<>(0);を読むよりもはるかに説明的です。


上記のコードを初めて読んでいて、ユーザーが実際に国を訪問したことがあるかどうかを確認するガード句につまずいたと想像してください。また、これが長いクラスに埋め込まれていると想像してくださいCollections.emptyList()と読む方が、 new ArrayList<>(0)と読むよりも明らかに説明的です。また、クライアント コードが変更できないように不変であることも確認します。

可能な限り最適に

自分の言語を理解し、それに応じて使用してください。 double必要な場合は、 Doubleオブジェクトでラップする必要はありません。 実際に必要なのはArrayだけである場合は、 Listを使用する場合も同様です。


スレッド間で状態を共有する場合は、 StringBuilderまたはStringBufferを使用して文字列を連結する必要があることに注意してください。


 // don't do this String votesByCounty = ""; for (County county : counties) { votesByCounty += county.toString(); } // do this instead StringBuilder votesByCounty = new StringBuilder(); for (County county : counties) { votesByCounty.append(county.toString()); }


データベースのインデックス作成方法を理解してください。ボトルネックを予測し、それに応じてキャッシュします。上記はすべて最適化です。これらは、第一人者として認識し、実装する必要がある種類の最適化です。

最初にどうやって殺すのか?

数年前に読んだハッキング記事のことは、決して忘れません。実を言うと、著者はすぐに撤回しましたが、それは、善意からいかに多くの悪が生まれるかを示しています。


 // do not do this, ever! int i = 0; while (i<10000000) { // business logic if (i % 3000 == 0) { //prevent long gc try { Thread.sleep(0); } catch (Ignored e) { } } }

地獄のガベージコレクターハック!


上記のコードがなぜ、どのように機能するかについては、 元の記事で詳しく読むことができます。このエクスプロイトは確かに興味深いものですが、これは決してしてはいけないことの 1 つです。


  • 副作用によって動作します。このブロックではThread.sleep(0)は意味がありません。
  • 下流のコードの欠陥を悪用して動作する
  • このコードを受け継ぐ人にとっては、それは難解で魔法のようだ


言語が提供するデフォルトの最適化をすべて使って記述した後でボトルネックに遭遇した場合にのみ、もう少し複雑なものを作成し始めてください。ただし、上記のような作り込みは避けてください。


Microsoft Copilot が「想像した」Java の将来のガベージ コレクターの解釈


ガベージコレクター対処するにはどうすればいいですか?

すべて完了した後でも、ガベージ コレクターがまだ抵抗を示している場合は、次のことを試してください。


  • サービスがレイテンシに非常に敏感で GC を許可できない場合は、 「Epsilon GC」を使用して実行し、GC を完全に回避します
    -XX:+UnlockExperimentalVMOptions -XX:+UseEpsilonGC


    これは明らかにOOM例外が発生するまでメモリを増大させるので、これは一時的なシナリオか、プログラムがオブジェクトを作成しないように最適化されているかのどちらかです。


  • サービスがレイテンシに敏感だが、許容範囲に余裕がある場合は、GC1 を実行し、 -XX:MaxGCPauseTimeMillis=100 (デフォルトは 250 ミリ秒) のような値を入力します。

  • 問題が外部ライブラリから発生する場合、たとえば、そのうちの1つがストップザワールドガベージコレクターであるSystem.gc()またはRuntime.getRuntime().gc()を呼び出す場合-XX:+DisableExplicitGCを指定して実行することで、問題の動作をオーバーライドできます。



バージョン開始

バージョン終了

デフォルトGC

ジャワ1

Java 4

シリアルガベージコレクター

Java 5

Java 8

並列ガベージコレクター

Java9 について

進行中

G1 ガベージコレクター


注 1: Java 15 以降、 ZGC本番環境で使用可能ですが、 -XX:+UseZGCを使用して明示的にアクティブ化する必要があります。


注 2: VM は、2 つ以上のプロセッサと 1792 MB 以上のヒープ サイズを検出すると、マシンをサーバー クラスと見なします。サーバー クラスでない場合は、 デフォルトでシリアル GC になります


基本的に、アプリケーションのパフォーマンス制約がガベージ コレクションの動作に直接関連していることが明らかで、十分な情報に基づいた調整を行うために必要な専門知識がある場合に、GC チューニングを選択します。それ以外の場合は、JVM のデフォルト設定を信頼し、アプリケーション レベルのコードの最適化に重点を置きます。

u/shiphe - コメント全文を読んでみてください


他にも調べてみたい関連ライブラリ:

Java マイクロベンチマーク ハーネス (JMH)

実際のベンチマークを行わずに感覚で最適化を行うと、自分自身に不利益をもたらします。JMH は、アルゴリズムのパフォーマンスをテストするための事実上のJava ライブラリです。これを使用してください。

Java スレッド アフィニティ

プロセスを特定のコアに固定すると、キャッシュ ヒットが改善される可能性があります。これは、基盤となるハードウェアと、ルーチンがデータを処理する方法によって異なります。とはいえ、このライブラリを使用すると実装が非常に簡単なので、CPU を集中的に使用するメソッドが問題になっている場合は、テストしてみることをお勧めします。

LMAXディスラプター

これは、たとえ必要でなくても、勉強したいと思うライブラリの 1 つです。その目的は、超低レイテンシの同時実行を可能にすることです。しかし、機械的な共感からリング バッファーまで、実装方法には多くの新しい概念がもたらされます。7 年前に初めてこのライブラリを発見したとき、それを理解するために徹夜したことを今でも覚えています。

Netflix jvmquake

jvmquakeの前提は、JVM に問題が発生した場合、JVM がハングアップするのではなく、停止することを望むというものです。数年前、私はメモリ制約が厳しい HTCondor クラスターでシミュレーションを実行していましたが、時々、「メモリ不足」エラーが原因でジョブが停止することがありました。


このライブラリは JVM を強制終了させ、実際のエラーに対処できるようにします。この特定のケースでは、HTCondor はジョブを自動的に再スケジュールします。

最終的な考え

私がこの記事を書くきっかけとなったコードとは?私はもっとひどいコードを書いたことがあります。今でも書いています。私たちが期待できる最善のことは、継続的に間違いを減らすことです。


数年後に自分のコードを見て不満を感じることになるだろうと予想しています。


それは良い兆候です。



編集と感謝:


wasteofserver.comにも掲載されています