gpt4 book ai didi

java - 为什么 Kotlin map-filter-reduce 在大输入上比 Java Stream 操作慢?

转载 作者:搜寻专家 更新时间:2023-11-01 00:59:31 25 4
gpt4 key购买 nike

几天前,我创建了一个简单的基准测试(没有 jmh 和所有其他专门的东西,只是为了粗略测量)。

我发现对于相同的简单任务(遍历 1000 万个数字,对它们求平方,仅过滤偶数并减少它们的总和),Java 的运行速度要快得多。这是代码:

Kotlin :

fun test() {
println((0 .. 10_000_000L).map { it * it }
.filter { it % 2 == 0L }
.reduce { sum, it -> sum + it })
}

Java:

public void test() {
System.out.println(LongStream.range(0, 10_000_000)
.map(it -> it * it)
.filter(it -> it % 2 == 0)
.reduce((sum, it) -> sum + it)
.getAsLong());
}

我使用的是 Java 1.8.0_144 版和 Kotlin 1.2 版。

在我的硬件上,Java 平均需要 85ms,Kotlin 需要 4,470ms 来执行相应的功能。 Kotlin 的运行速度慢了 52 倍。

我怀疑 Java 编译器会生成优化的字节码,但我没想到会看到如此巨大的差异。我想知道我是否做错了什么?我怎样才能迫使 Kotlin 工作得更快?我喜欢它是因为它的语法,但 52 次是一个很大的不同。而且我只是编写了类似 Java 8 的代码,而不是普通的旧迭代版本(我相信它会比给定的版本快得多)。

最佳答案

当您将苹果与橙子进行比较时,结果并不能告诉您太多信息。您将一个 API 与另一个 API 进行了比较,每个 API 都有完全不同的重点和目标。

由于 JDK 的所有部分与特定于 Kotlin 的添加一样都是“Kotlin”,因此我写了更多的同类比较,这也解决了一些“JVM 微基准测试”问题。

Kotlin :

fun main(args: Array<String>) {
println("Warming up Kotlin")
test()
test()
test()
println("Measuring Kotlin")
val average = (1..10).map {
measureTimeMillis { test() }
}.average()
println("An average Kotlin run took $average ms")
println("(sum is $sum)")
}

var sum = 0L

fun test() {
sum += LongStream.range(0L, 100_000_000L)
.map { it * it }
.filter { it % 2 == 0L }
.reduce { sum, it -> sum + it }
.asLong
}

Java:

public static void main(String[] args) {
System.out.println("Warming up Java");
test();
test();
test();
System.out.println("Measuring Java");
LongSummaryStatistics stats = LongStream.range(0, 10)
.map(i -> measureTimeMillis(() -> test()))
.summaryStatistics();
System.out.println("An average Java run took " + stats.getAverage() + " ms");
System.out.println("sum is " + sum);

}

private static long sum;

private static void test() {
sum += LongStream.range(0, 100_000_000)
.map(it -> it * it)
.filter(it -> it % 2 == 0)
.reduce((sum, it) -> sum + it)
.getAsLong();
}

private static long measureTimeMillis(Runnable measured) {
long start = System.nanoTime();
measured.run();
return TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);
}

我的结果:

Warming up Kotlin
Measuring Kotlin
An average Kotlin run took 158.5 ms
(sum is 4276489111714942720)


Warming up Java
Measuring Java
An average Java run took 357.3 ms
sum is 4276489111714942720

惊讶?我也是。

与其进一步挖掘,试图弄清楚预期结果的这种反转,我想得出这样的结论:

Kotlin 在 Iterable 上的 FP 扩展是为了方便起见。在 95% 的所有用例中,您不关心在 10-100 个元素的列表上执行快速映射过滤器是否需要 1 或 2 微秒。

Java 的 Stream API 专注于对大型数据结构进行批量操作的性能。它还为同一目标提供自动并行化(尽管它几乎从来没有真正帮助过),但由于这些问题,它的 API 是有缺陷的,有时甚至很尴尬。例如,许多有用的操作恰好没有很好地并行化,并且不存在,并且非终端与终端操作的整个范例增加了您编写的每个 Streams 表达式的体积。


让我再谈谈你的一些陈述:

I know that the Java compiler produces optimized bytecode

这是 a) 不正确的,b) 很大程度上无关紧要,因为(几乎)没有“优化字节码”这样的东西。字节码的解释执行总是比 JIT 编译的 native 代码至少慢一个数量级。

And I just wrote Java 8-like code, not the plain old iterative version (which, I believe, will be much faster than given one).

你是这个意思吗?

Kotlin :

fun test() {
var sum: Long = 0
var i: Long = 0
while (i < 100_000_000) {
val j = i * i
if (j % 2 == 0L) {
sum += j
}
i++
}
total += sum
}

Java:

private static void test() {
long sum = 0;
for (long i = 0; i < 100_000_000; i++) {
long j = i * i;
if (j % 2 == 0) {
sum += j;
}
}
total += sum;
}

结果如下:

Warming up Kotlin
Measuring Kotlin
An average Kotlin run took 150.1 ms
(sum is 4276489111714942720)

Warming up Java
Measuring Java
An average Java run took 153.0 ms
sum is 4276489111714942720

在这两种语言中,性能几乎与上面的 Kotlin + Streams API 相同。如前所述,Streams API 针对性能进行了优化。

kotlincjavac 都可能生成非常相似的字节码,给定这个简单的源代码,然后 HotSpot 以相同的方式完成它的工作。

关于java - 为什么 Kotlin map-filter-reduce 在大输入上比 Java Stream 操作慢?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48317709/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com