Serializable lambdas performance in Java 8

I read in some comments by Brian Goetz that serializable lambdas "have significantly higher operating costs compared to non-serializable lambdas."

I'm wondering now: where exactly is this overhead and what causes them? Does this affect only the creation of lambda, as well as the challenge?

In the code below, would both cases (callExistingInstance () and callWithNewInstance ()) be affected by the serializability of "MyFunction" or just the second case?

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); } } 
+5
source share
2 answers

Performance degradation occurs when you serialize / deserialize and when you instantiate. Only your second example takes a hit. The reason this is expensive is because when you deserialize, the base class of your lambda is created by some special reflection (which has the ability to create / define a class) rather than a simple old serialized object (where will the class definition from? ), as well as perform some security checks ...

+3
source

Typically, the executable part of a lambda implementation will generate a class that will mainly consist of one implementation method. The information needed to create such a class is provided when the LambdaMetafactory.metafactory bootstrap method is LambdaMetafactory.metafactory at run time.

When you turn on serialization, things get complicated. Firstly, the compiled code will use the alternative bootstrap method LambdaMetafactory.altMetafactory , which provides more flexibility for the price associated with the need to analyze the varargs parameters according to the flags specified in the parameter array.

Then the generated lambda class must have a writeReplace method (see the second half of the Serializable documentation ), which has to create and return an instance of SerializedLambda , which contains all the information needed to recreate the lambda instance. Since the only method for implementing the lambdas class is to simply delegate delegation, the writeReplace method and related information will multiply the generated class size.

It's also worth noting that your class creating this instance of lambda Serializable will have a synthetic method $deserializeLambda$ (compare the SerializedLambda documentation as an analogue of the lambdas writeReplace process, which will increase the disk usage and load time of your class (but will not affect the lambda- expressions).


In your code example, both methods will be affected by the same amount of time as the bootstrap, and class generation occurs only once per lambda expression. In subsequent evaluations, the class generated during the first evaluation will be reused and only a new instance will be created (even if the instance is not reused) . We are talking about a single flood here, even if the lambda expression is contained in a loop, it only affects the first iteration.

Note that if there is a lambda expression in the loop, there may be a new instance created for each iteration, while being outside the loop will probably have one instance in the whole loop. But this behavior does not depend on the question of whether the target interface is Serializable . It just depends on whether the expression reflects the meaning (compare with this answer ).

Please note that if you wrote

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

in your second method (note the explicit final modifier), the toAdd value will be a compile-time constant, and the expression will be compiled just like you wrote (number) -> number + 1 , that is, you will no longer write the value. Then you will get the same lambda instance in each iteration of the loop (with the current version of the JVM Oracles). Therefore, the question of whether a new instance is created sometimes depends on small bits of context. But, as a rule, the impact of performance is rather small.

+2
source

Source: https://habr.com/ru/post/1210578/


All Articles