当你想发送一个匿名函数时,做一个 (Runnable & Serializable) 是不是太昂贵了?
Is it too costly to do a (Runnable & Serializable) when you want to send an anonymous function?
我正在这样做:
executor.execute((Runnable & Serializable)func);
其中 func 是一个匿名函数,我必须在项目中大量使用它,否则我将不得不为每个我想调用的不同函数创建一个 class 并在每个函数中实现 Runnable 和 Serializable class,优点是我会在编译时拥有类型而不是在 运行 时转换它,我想知道做这个转换是否太昂贵或微不足道并且不代表性能差距很大。
如果你有这方面的真实生活经验并且愿意分享,那就太棒了。
提前致谢。
在大多数情况下,在 运行 时执行任何操作最终都会比其编译时替代方案成本更高。在某些情况下这不是真的,但大多数情况下 JVM 做得更好。对于我见过的大多数此类实施情况,这样做的成本更高。最后,这实际上取决于您将 运行 完成的任务数量。如果它们很多,我建议在这里使用接口或抽象。类似于....
public interface RunnableSerializable extends Runnable, Serializable {
// override and add as necessary
}
public class MyRunnableClass implementes RunnableSerializable {
// your runnable code
}
MyRunnableClass clazz = ...
executor.execute(clazz);
如果每隔几分钟(或更长时间)只有很少的 运行nables,类型转换应该没问题。
转换是 "almost trivial",因为它仅验证给定对象是否扩展了一些 class 或实现了接口 - 它不会更改对象本身。
在你的例子中,如果 func
代表一个 lambda 表达式或一个方法引用
executor.execute((Runnable & Serializable) () -> System.out.println("5"));
executor.execute((Runnable & Serializable) System.out::println);
LambdaMetafactory 保证生成的 lambda 对象真正实现了 Runnable
和 Serializable
,甚至可以优化转换。
如果 func
是您方法的参数:
public void execute(Runnable func) {
executor.execute((Runnable & Serializable)func);
}
java 编译器和 java 运行时都不会神奇地使 func
也可序列化。
在这种情况下,您可以将方法重写为
public <T extends Runnable & Serializable> void execute(T func) {
executor.execute(func);
}
这要求调用者提供一个 Runnable 和 Serializable 对象 - 一个自动生成的对象(通过 lambda 表达式或方法引用)或一个 "manually" 编码的 class.
演员本身真的很微不足道:它只是一个 if-else,它会被 JIT 和分支预测器优化,因为你一直在传递有效的 classes(你没有看到任何 ClassCastException 的).
我认为真正的问题是执行(通过 Runnable.run() 通过接口进行虚拟调用)匿名 lambda 或声明的 class 之间是否存在任何区别。所以我设置了一个 JMH 基准来测试以下 3 个使用 lambda 的情况并声明 classes:
- 执行任务
- 随机执行不同的东西(以防止分支预测)
- 随机执行不同的东西,但有些东西会更频繁地出现(允许分支预测,如果有的话)
结果显示 lambda 慢了纳秒级,所以看起来 labmda 和声明的 class 之间没有区别:
Benchmark Mode Cnt Score Error Units
MyBenchmark.testAnonymousLambda sample 7272891 16.150 ± 1.646 ns/op
MyBenchmark.testDeclared sample 7401499 15.349 ± 3.648 ns/op
MyBenchmark.testRandomAnonymousLambda sample 6851255 3314.151 ± 11.655 ns/op
MyBenchmark.testRandomBranchingDeclared sample 6887926 3293.818 ± 9.180 ns/op
MyBenchmark.testPredictableAnonymousLambda sample 3990711 6091.766 ± 25.912 ns/op
MyBenchmark.testPredictableBranchingDeclared sample 3993885 6055.421 ± 21.535 ns/op
因此,在回答您的问题时,您是否会转换或是否会使用 lambda 而不是创建一组已声明的 classes 并不重要。
P.S。基准代码可通过 a gist
获得
我正在这样做:
executor.execute((Runnable & Serializable)func);
其中 func 是一个匿名函数,我必须在项目中大量使用它,否则我将不得不为每个我想调用的不同函数创建一个 class 并在每个函数中实现 Runnable 和 Serializable class,优点是我会在编译时拥有类型而不是在 运行 时转换它,我想知道做这个转换是否太昂贵或微不足道并且不代表性能差距很大。
如果你有这方面的真实生活经验并且愿意分享,那就太棒了。
提前致谢。
在大多数情况下,在 运行 时执行任何操作最终都会比其编译时替代方案成本更高。在某些情况下这不是真的,但大多数情况下 JVM 做得更好。对于我见过的大多数此类实施情况,这样做的成本更高。最后,这实际上取决于您将 运行 完成的任务数量。如果它们很多,我建议在这里使用接口或抽象。类似于....
public interface RunnableSerializable extends Runnable, Serializable {
// override and add as necessary
}
public class MyRunnableClass implementes RunnableSerializable {
// your runnable code
}
MyRunnableClass clazz = ...
executor.execute(clazz);
如果每隔几分钟(或更长时间)只有很少的 运行nables,类型转换应该没问题。
转换是 "almost trivial",因为它仅验证给定对象是否扩展了一些 class 或实现了接口 - 它不会更改对象本身。
在你的例子中,如果 func
代表一个 lambda 表达式或一个方法引用
executor.execute((Runnable & Serializable) () -> System.out.println("5"));
executor.execute((Runnable & Serializable) System.out::println);
LambdaMetafactory 保证生成的 lambda 对象真正实现了 Runnable
和 Serializable
,甚至可以优化转换。
如果 func
是您方法的参数:
public void execute(Runnable func) {
executor.execute((Runnable & Serializable)func);
}
java 编译器和 java 运行时都不会神奇地使 func
也可序列化。
在这种情况下,您可以将方法重写为
public <T extends Runnable & Serializable> void execute(T func) {
executor.execute(func);
}
这要求调用者提供一个 Runnable 和 Serializable 对象 - 一个自动生成的对象(通过 lambda 表达式或方法引用)或一个 "manually" 编码的 class.
演员本身真的很微不足道:它只是一个 if-else,它会被 JIT 和分支预测器优化,因为你一直在传递有效的 classes(你没有看到任何 ClassCastException 的).
我认为真正的问题是执行(通过 Runnable.run() 通过接口进行虚拟调用)匿名 lambda 或声明的 class 之间是否存在任何区别。所以我设置了一个 JMH 基准来测试以下 3 个使用 lambda 的情况并声明 classes:
- 执行任务
- 随机执行不同的东西(以防止分支预测)
- 随机执行不同的东西,但有些东西会更频繁地出现(允许分支预测,如果有的话)
结果显示 lambda 慢了纳秒级,所以看起来 labmda 和声明的 class 之间没有区别:
Benchmark Mode Cnt Score Error Units
MyBenchmark.testAnonymousLambda sample 7272891 16.150 ± 1.646 ns/op
MyBenchmark.testDeclared sample 7401499 15.349 ± 3.648 ns/op
MyBenchmark.testRandomAnonymousLambda sample 6851255 3314.151 ± 11.655 ns/op
MyBenchmark.testRandomBranchingDeclared sample 6887926 3293.818 ± 9.180 ns/op
MyBenchmark.testPredictableAnonymousLambda sample 3990711 6091.766 ± 25.912 ns/op
MyBenchmark.testPredictableBranchingDeclared sample 3993885 6055.421 ± 21.535 ns/op
因此,在回答您的问题时,您是否会转换或是否会使用 lambda 而不是创建一组已声明的 classes 并不重要。
P.S。基准代码可通过 a gist
获得