LinkedList の方が高速でしょうか? `for each` を `iterator` に置き換えるべきでしょうか? この `ArrayList` は `Array` にすべきでしょうか? この記事は、非常に悪意のある最適化への対応として書かれたもので、私の記憶に永久に刻み込まれています。
Java と、ガベージ コレクターまたはコンテキスト切り替えによる干渉に対処する方法について詳しく説明する前に、まず将来の自分のために、コード記述の基礎について簡単に説明します。
時期尚早な最適化は諸悪の根源です。
これまでにも聞いたことがあると思いますが、時期尚早な最適化は諸悪の根源です。まあ、時々はそうなります。ソフトウェアを作成するときは、次の点を強く信じています。
できるだけ詳細に、物語を書いているかのように意図を語るようにしてください。
可能な限り最適にする必要があります。つまり、言語の基礎を理解し、それに応じて適用する必要があります。
コードは意図を表す必要があり、その多くはメソッドや変数に名前を付ける方法に関係しています。
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)
は意味がありません。
言語が提供するデフォルトの最適化をすべて使って記述した後でボトルネックに遭遇した場合にのみ、もう少し複雑なものを作成し始めてください。ただし、上記のような作り込みは避けてください。
すべて完了した後でも、ガベージ コレクターがまだ抵抗を示している場合は、次のことを試してください。
サービスがレイテンシに非常に敏感で GC を許可できない場合は、 「Epsilon GC」を使用して実行し、GC を完全に回避します。
-XX:+UnlockExperimentalVMOptions -XX:+UseEpsilonGC
これは明らかにOOM例外が発生するまでメモリを増大させるので、これは一時的なシナリオか、プログラムがオブジェクトを作成しないように最適化されているかのどちらかです。
サービスがレイテンシに敏感だが、許容範囲に余裕がある場合は、GC1 を実行し、 -XX:MaxGCPauseTimeMillis=100
(デフォルトは 250 ミリ秒) のような値を入力します。
問題が外部ライブラリから発生する場合、たとえば、そのうちの1つがストップザワールドガベージコレクターであるSystem.gc()
またはRuntime.getRuntime().gc()
を呼び出す場合-XX:+DisableExplicitGC
を指定して実行することで、問題の動作をオーバーライドできます。
-XX:+UnlockExperimentalVMOptions -XX:+UseZGC
。このJDK 21 GC ベンチマークも確認することをお勧めします。
バージョン開始 | バージョン終了 | デフォルト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 - コメント全文を読んでみてください
実際のベンチマークを行わずに感覚で最適化を行うと、自分自身に不利益をもたらします。JMH は、アルゴリズムのパフォーマンスをテストするための事実上のJava ライブラリです。これを使用してください。
プロセスを特定のコアに固定すると、キャッシュ ヒットが改善される可能性があります。これは、基盤となるハードウェアと、ルーチンがデータを処理する方法によって異なります。とはいえ、このライブラリを使用すると実装が非常に簡単なので、CPU を集中的に使用するメソッドが問題になっている場合は、テストしてみることをお勧めします。
これは、たとえ必要でなくても、勉強したいと思うライブラリの 1 つです。その目的は、超低レイテンシの同時実行を可能にすることです。しかし、機械的な共感からリング バッファーまで、実装方法には多くの新しい概念がもたらされます。7 年前に初めてこのライブラリを発見したとき、それを理解するために徹夜したことを今でも覚えています。
jvmquake
の前提は、JVM に問題が発生した場合、JVM がハングアップするのではなく、停止することを望むというものです。数年前、私はメモリ制約が厳しい HTCondor クラスターでシミュレーションを実行していましたが、時々、「メモリ不足」エラーが原因でジョブが停止することがありました。
このライブラリは JVM を強制終了させ、実際のエラーに対処できるようにします。この特定のケースでは、HTCondor はジョブを自動的に再スケジュールします。
私がこの記事を書くきっかけとなったコードとは?私はもっとひどいコードを書いたことがあります。今でも書いています。私たちが期待できる最善のことは、継続的に間違いを減らすことです。
数年後に自分のコードを見て不満を感じることになるだろうと予想しています。
それは良い兆候です。
visitedCountries()
の可変性エラーをキャッチし、詳細な説明をしてくれたFlorianSchaetzに感謝します。wasteofserver.comにも掲載されています