我有以下课程:
public final class App {
private App() {
}
public static void main(String[] args) {
Stopwatch stopwatch = Stopwatch.createStarted();
new App().main();
System.out.println(((double) stopwatch.elapsed(TimeUnit.MICROSECONDS) * 1_000_000) + " seconds!");
}
private void main() {
List<Integer> list = new ArrayList<>();
for (int i = 0; i < 1000000; i++) {
list.add(ThreadLocalRandom.current().nextInt(1000));
}
System.out.println(minNoUnboxing(list));
System.out.println(minWithUnboxing(list));
}
private Integer minNoUnboxing(List<Integer> list) {
return list.stream().min(Integer::compareTo).orElse(-1);
}
private Integer minWithUnboxing(List<Integer> list) {
return list.stream().mapToInt(x -> x).min().orElse(-1);
}
}这个类有两个方法,它们接收整数列表并返回最小的数字。一种方法是将Integer的compareTo()方法作为min()函数中的比较器传递。另一种方法是从列表中获取一个IntStream,并对其调用min()函数。
第二种方法使用取消装箱来映射包装的int。取消装箱是著名的缓慢时,使用频繁,但我看不到使用和不使用它在这个程序之间的区别。
哪条路更快?或者他们俩是一样的?
谢谢。
编辑:
我采纳了Code-学徒的建议,并使用以下方法进行了大量测量:
Stopwatch noUnboxing = Stopwatch.createStarted();
for (int i = 0; i < 1000; i++) {
minNoUnboxing(list);
}
System.out.println((double) noUnboxing.elapsed(TimeUnit.MILLISECONDS) / 1000 + " no unboxing seconds");
Stopwatch withUnboxing = Stopwatch.createStarted();
for (int i = 0; i < 1000; i++) {
minWithUnboxing(list);
}
System.out.println((double) withUnboxing.elapsed(TimeUnit.MILLISECONDS) / 1000 + " with unboxing seconds");事实上,开箱速度比第一种方法快2倍。为什么会这样呢?
输出:
4.166 no unboxing seconds
1.922 with unboxing seconds发布于 2020-09-29 16:43:29
取消装箱只不过是读取Integer对象的Integer字段的值。这不能减缓操作的速度,至于与其他变体中的Integer实例相比,也必须读取这些字段。
因此,这些操作在不同的抽象上工作。
当您使用mapToInt(x -> x)时,您使用的是一个ToIntFunction来告诉实现如何获取int值,然后,min操作直接在int值上工作。
当您使用min(Integer::compareTo)时,您使用的是一个Comparator来告诉泛型实现,哪个对象比另一个小。
基本上,这些操作相当于
private Optional<Integer> minNoUnboxing(List<Integer> list) {
Comparator<Integer> c = Integer::compareTo;
if(list.isEmpty()) return Optional.empty();
Integer o = list.get(0);
for(Integer next: list.subList(1, list.size())) {
if(c.compare(o, next) > 0) o = next;
}
return Optional.of(o);
}
private OptionalInt minWithUnboxing(List<Integer> list) {
ToIntFunction<Integer> toInt = x -> x;
if(list.isEmpty()) return OptionalInt.empty();
int i = toInt.applyAsInt(list.get(0));
for(Integer next: list.subList(1, list.size())) {
int nextInt = toInt.applyAsInt(next);
if(i > nextInt) i = nextInt;
}
return OptionalInt.of(i);
}除非运行时优化器消除了所有差异,否则对于较大的列表,我希望取消装箱版本会更快,因为取消装箱为每个元素提取一次int字段,而compareTo必须为每个比较提取两个int值。
发布于 2020-09-29 17:23:23
性能影响几乎与解除装箱几乎没有任何关系,与您正在比较两个根本不同的操作(比较器与缩减的最小化)有关。
见以下基准:
@Benchmark
public Integer minNoUnboxing(BenchmarkState state) {
return state.randomNumbers.stream().min(Integer::compareTo).orElse(-1);
}
@Benchmark
public Integer minNoUnboxingReduce(BenchmarkState state) {
return state.randomNumbers.stream().reduce((a, b) -> a < b ? a : b).orElse(-1);
}
@Benchmark
public Integer minWithUnboxingReduce(BenchmarkState state) {
return state.randomNumbers.stream().mapToInt(x -> x).min().orElse(-1);
}结果:
Benchmark (listSize) Mode Cnt Score Error Units
MyBenchmark.minNoUnboxing 1000000 thrpt 5 128.585 ± 17.617 ops/s
MyBenchmark.minNoUnboxingReduce 1000000 thrpt 5 317.772 ± 27.659 ops/s
MyBenchmark.minWithUnboxingReduce 1000000 thrpt 5 300.348 ± 23.458 ops/s编辑:还请注意,与装箱相比,取消装箱非常快速。在最坏的情况下,取消装箱只是字段访问/指针取消引用,而装箱则可能涉及对象实例化。
https://stackoverflow.com/questions/64122809
复制相似问题