我在Brian的一些评论中看到,可序列化的lambdas“比不可序列化的lambdas具有更高的性能成本”。
我现在很好奇:这到底是在哪里?是什么造成的?它是只影响lambda的实例化,还是在调用中影响?
在下面的代码中,这两种情况( callExistingInstance()和callWithNewInstance() )是否都会受到"MyFunction“的可序列化性的影响,还是只受第二种情况的影响?
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);
}
}发布于 2015-01-07 16:19:37
当您序列化/反序列化和实例化时,性能就会受到影响。只有你的第二个例子受到打击。其原因是,当反序列化时,lambda的底层类被某种特殊反射(它能够创建/定义类)实例化,而不是普通的旧序列化对象(类定义来自哪里?),以及执行一些安全检查.
发布于 2015-01-12 17:49:27
通常,lambda实现的运行时部分将生成一个类,该类基本上由一个实现方法组成。生成这样一个类所需的信息是通过在运行时对LambdaMetafactory.metafactory的引导方法调用提供的。
当启用序列化时,事情会变得更加复杂。首先,编译后的代码将使用替代的引导方法LambdaMetafactory.altMetafactory,它提供了更大的灵活性,代价是必须根据参数数组中指定的标志解析varargs参数。
然后,生成的lambda类必须有一个writeReplace方法(参见 documentation的下半部分),该方法必须创建并返回一个包含重新创建lambda实例所需的所有信息的SerializedLambda实例。由于lambda类的单个实现方法只包含一个简单的委托调用,所以writeReplace方法和相关的常量信息将乘以生成的类的大小。
还值得注意的是,创建可序列化的lambda实例的类将有一个合成方法$deserializeLambda$ (将SerializedLambda与lambda的writeReplace进程相比较。这将增加类的磁盘使用量和加载时间(但不会影响lambda表达式的计算)。
在示例代码中,这两个方法都会受到与引导和类生成相同的时间的影响,每个lambda表达式只发生一次。在随后的评估中, created (if not even the instance is re-used)。我们在这里讨论的是一次性开销,即使lambda表达式包含在循环中,它也只影响第一次迭代。
请注意,如果在循环中有一个lambda表达式,则可能会为每个迭代创建一个新实例,而在整个循环期间,在循环外使其具有一个实例。但这种行为并不取决于目标接口是否为Serializable的问题。这仅仅取决于表达式是否捕获值(与this answer相比)。
注意如果你写了
final long toAdd = 1;
MyFunction<Long, Long> adder = (number) -> number + toAdd;在第二个方法(请注意显式final修饰符)中,值toAdd将是一个编译时常量,如果您编写了(number) -> number + 1,即不再捕获值,表达式就会被编译。然后,您将在每个循环迭代中获得相同的lambda实例(使用Oracle当前版本的JVM)。因此,新实例是否创建的问题有时取决于上下文的一些小部分。但通常情况下,对性能的影响相当小。
https://stackoverflow.com/questions/27823516
复制相似问题