11

次のスタイルのコードについてコメントがありました。

Iterable<String> upperCaseNames = Iterables.transform(
    lowerCaseNames, new Function<String, String>() {
        public String apply(String input) {
            return input.toUpperCase();
        }
    });

その人は、このコードを実行するたびに、この匿名の Function クラスをインスタンス化するので、たとえば静的変数に単一のインスタンスを作成する必要があると言いました。

static Function<String, String> toUpperCaseFn =
    new Function<String, String>() {
        public String apply(String input) {
            return input.toUpperCase();
        }
    };
...
Iterable<String> upperCaseNames =
    Iterables.transform(lowerCaseNames, toUpperCaseFn);

非常に表面的なレベルでは、これはどういうわけか理にかなっています。クラスを複数回インスタンス化すると、メモリなどを浪費する必要がありますよね?

一方、人々はコードの途中で無名クラスをインスタンス化し、あたかも明日がないかのようにします。コンパイラがこれを最適化するのは簡単なことです。

これは正当な懸念事項ですか?

4

3 に答える 3

8

ホット スポット JVM の最適化に関する面白い事実です。現在のメソッドの外で渡されないオブジェクトをインスタンス化すると、JVM はバイトコード レベルで最適化を実行します。

通常、スタック割り当ては、C++ などのメモリ モデルを公開する言語に関連付けられています。deleteスコープが終了すると自動的に割り当てが解除されるため、C++ で変数をスタックする必要はありません。これは、使い終わったらポインターを削除する必要があるヒープ割り当てとは対照的です。

Hot Spot JVM では、オブジェクトがスレッドを「エスケープ」できるかどうかを判断するために、バイトコードが分析されます。エスケープには3 つのレベルがあります。

  1. エスケープなし - オブジェクトは作成されたメソッド/スコープ内でのみ使用され、オブジェクトはスレッド外ではアクセスできません。
  2. ローカル/引数エスケープ - オブジェクトは、それを作成するメソッドによって返されるか、それが呼び出すメソッドに渡されますが、これらのメソッドのいずれも、そのオブジェクトをスレッド外でアクセスできる場所に配置しません。
  3. グローバル エスケープ - オブジェクトは、別のスレッドでアクセスできる場所に配置されます。

これは基本的に、1) 別のメソッドに渡すか返すか、および 2) GC ルートにアタッチされたものやフィールドClassLoaderに格納されたものに関連付けるかという質問に似ています。static

特定のケースでは、匿名オブジェクトは「ローカルエスケープ」としてタグ付けされます。これはsynchronized、オブジェクトのロック (読み取り: の使用) が最適化されて取り除かれることを意味するだけです。(別のスレッドで使用されることのないものを同期するのはなぜですか?) これは、スタック上で割り当てを行うエスケープなし」とは異なります。この「割り当て」はヒープ割り当てと同じではないことに注意することが重要です。実際に行うことは、非エスケープ オブジェクト内のすべての変数にスタック上のスペースを割り当てることです。no-escape オブジェクト内にintString、の3 つのフィールドがある場合、 、参照、および参照3つのスタック変数が割り当てられます。MyObjectintStringMyObject MyObjectインスタンス自体は、「エスケープなし」と分析されない限り、ヒープに格納されます。次に、オブジェクトの割り当てが最適化され、コンストラクター/メソッドがヒープ変数の代わりにローカル スタック変数を使用して実行されます。

そうは言っても、私には時期尚早の最適化のように思えます。コードが遅く、パフォーマンスの問題を引き起こしていることが後で証明されない限り、可読性を低下させるようなことは何もすべきではありません。私にとって、このコードはかなり読みやすいので、そのままにしておきます。もちろん、これは完全に主観的なものですが、「パフォーマンス」は、実際の実行時間と関係がない限り、コードを変更する正当な理由にはなりません。通常、最適化が時期尚早であると、コードの保守が難しくなり、パフォーマンス上のメリットは最小限に抑えられます。

Java 8+ とラムダ

匿名インスタンスの割り当てがまだ気になる場合は、単一の抽象メソッド (SAM) 型にラムダを使用するように切り替えることをお勧めします。Lambda 評価は を使用invokedynamicして実行され、実装は最初の呼び出しで Lambda の単一インスタンスのみを作成することになります。詳細についてはこちらの回答とこちらの回答をご覧ください。SAM 以外のタイプの場合でも、匿名インスタンスを割り当てる必要があります。ここでのパフォーマンスへの影響は、ほとんどのユースケースで無視できますが、IMO では、この方法の方が読みやすいです。

参考文献

于 2013-10-31T23:44:12.337 に答える
1

簡単な答え: いいえ - 心配しないでください。

長い答え: インスタンス化する頻度によって異なります。頻繁に呼び出されるタイトなループの場合、関数が適用されると、関数String.toUpperCase()内のすべてのアイテムに対して 1 回呼び出されることに注意してください。Iterableおそらく、呼び出しごとに新しいStringが作成され、GC チャーンがはるかに多くなります。

「時期尚早の最適化は諸悪の根源です」 - クヌース

于 2013-10-31T23:40:10.250 に答える
1

このスレッドを見つけました: Java 匿名クラスの効率の影響、興味深いと思うかもしれません

マイクロベンチマークを行いました。マイクロベンチマークは、ループ反復ごとに (静的内部) クラスをインスタンス化する、(静的内部) クラスを 1 回インスタンス化してループ内で使用する、および匿名クラスを使用する 2 つの同様のものを比較しました。マイクロ ベンチマークでは、コンパイラは匿名クラスをループから抽出したように見え、予測どおり、匿名クラスを呼び出し元の内部クラスに昇格させました。これは、4 つの方法すべての速度に違いがないことを意味します。また、外部クラスと比較しましたが、やはり同じ速度です。匿名クラスを持つものは、おそらく〜128ビットのスペースを必要としました

http://jdmaguire.ca/Code/Comparing.java & http://jdmaguire.ca/Code/OutsideComp.javaで私のマイクロベンチマークを確認できます。wordLen、sortTimes、listLen のさまざまな値でこれを実行しました。また、JVM はウォームアップが遅いため、メソッド呼び出しをシャッフルしました。コメントのないひどいコードで私を判断しないでください。私はRLよりも上手にプログラミングします。また、マイクロベンチングのマーキングは、時期尚早の最適化と同じくらい邪悪で役に立たないものです。

于 2013-11-01T00:29:05.223 に答える