2015-01-07 23 views
5

シリアライズ可能なラムダが「シリアル化できないラムダと比較してパフォーマンスが大幅に向上している」とのコメントをBrian Goetzが読みました。Javaでシリアライズ可能なラムダのパフォーマンス8

私は今興味があります:正確にそのオーバーヘッドとは何ですか?それはラムダのインスタンス化にのみ影響しますか、または呼び出しにも影響しますか?

以下のコードでは、callExistingInstance()とcallWithNewInstance()の両方のケースで、「MyFunction」のシリアル化機能、または2番目のケースのみが影響を受けますか?

interface MyFunction<IN, OUT> { 
    OUT call(IN arg); 
} 

void callExistingInstance() { 

    long toAdd = 1; 
    long value = 0; 

    final MyFunction<Long, Long> adder = (number) -> number + toAdd; 

    for (int i = 0; i < LARGE_NUMBER; i++) { 
     value = adder.call(value); 
    } 
} 

void callWithNewInstance() { 

    long value = 0; 

    for (int i = 0; i < LARGE_NUMBER; i++) { 
     long toAdd = 1; 

     MyFunction<Long, Long> adder = (number) -> number + toAdd; 

     value = adder.call(value); 
    } 
} 

答えて

3

パフォーマンスのヒットは、シリアル化/逆シリアル化とインスタンス化時に発生します。 2番目の例だけがヒットします。それは高価な理由は、あなたのラムダの基底クラスは、普通の古いシリアライズされたオブジェクトではなく、(クラスを作成/定義する能力を持つ)一種の特別なリフレクションによってインスタンス化されるということです?)。また、いくつかのセキュリティチェックを実行します。

+0

すてきな説明ありがとうございました。 –

+0

この説明は問題には当てはまりません。デシリアライズするオブジェクトは、ラムダ式を使用するかどうかに関係なく、常に高価なReflectionを使用します。しかし、質問のコードは何かを逆直列化しません。 – Holger

+0

問題は、他のタイプのlamdbasではなく、シリアライズ可能なlambdaをシリアライズして使用することでした。 OPは、これらのオブジェクトのライフサイクルのどの部分が通常のラムダよりパフォーマンスが低いかを尋ねていました。彼のコードは呼び出しとインスタンス化を正確に区別しています。答えが示されているように、彼の症例の1つが影響を受ける。ラムダをデシリアライズすることは、他のオブジェクトをデシリアライズすること(「特別なリフレクション」)よりも非常に異なるものであり、本質的に高価です。 – BadZen

2

通常、ラムダ実装のランタイム部分は、基本的に単一の実装方法からなるクラスを生成します。そのようなクラスを生成するために必要な情報は、実行時にLambdaMetafactory.metafactoryへのブートストラップメソッドの呼び出しによって与えられます。

シリアル化を有効にすると、状況がより複雑になります。まず、コンパイルされたコードは、代替ブートストラップメソッドLambdaMetafactory.altMetafactoryを使用します。これは、パラメータ配列内で指定されたフラグに基づいてvarargsパラメータを解析する必要がなく、柔軟性が高くなります。

が生成ラムダ・クラスを作成およびラムダ・インスタンスを再作成するために必要なすべての情報を含むSerializedLambdaインスタンスを戻すために有するwriteReplace方法(Serializable documentationの後半を参照)を有していなければなりません。ラムダクラスの単一の実装方法は単純な委譲呼び出しのみからなるので、そのwriteReplaceメソッドと関連する定数情報は、生成されたクラスのサイズを乗算します。

また、ラムダのwriteReplaceのプロセスへの対応としてclass documentation of SerializedLambdaを比較する(つまりシリアライズラムダインスタンスを作成し、あなたのクラスは、合成法$deserializeLambda$を持っていることは注目に値します。あなたのクラスのディスクの使用状況とロード時間を増やす(ただし、しないこと)ラムダ式の評価に影響を与える。


をあなたのコード例では、両方の方法は、ブートストラップクラスの生成など、同じ時間によって影響を受けるラムダ式ごとに一度だけ行われます。その後の評価に、the class generated on the first evaluation will be re-used and only a new instance created (if not even the instance is re-used)私たちはここで一度のオーバーヘッドについて言及しています。 da式はループに含まれ、最初の反復のみに影響を与えます。あなたがループ内でラムダ式を持っている場合は、そこ確かためは、ループ全体中に1つのインスタンスを持つことになりますループの外にそれを持ちながら反復ごとに作成される新しいインスタンスかもしれないと

注意。しかし、この動作は、ターゲットインターフェイスがSerializableかどうかという問題には依存しません。式が値を取り込むかどうか(単にthis answerと比較)によって異なります。あなたが(明示的final修飾子に注意してください)あなたの第二の方法では

final long toAdd = 1; 
MyFunction<Long, Long> adder = (number) -> number + toAdd; 

を書いていた場合、値toAddはコンパイル時の定数となり、あなたが(number) -> number + 1を書いていた場合、式は同じようにコンパイルされることを

は注意、つまり、もはや値を取得しません。次に、各ループ反復で同じlambdaインスタンスを取得します(現在のバージョンのOracle JVMを使用)。したがって、新しいインスタンスが作成されるかどうかという疑問は、コンテキストの小さなビットに依存することがあります。しかし、通常、パフォーマンスの影響はやや小さいです。