为什么 Iterable.sum() 在 Kotlin 中很慢?



我注意到Itarable.sum()的性能与手动求和的直接循环之间存在惊人的差异。考虑一下:

import kotlin.system.measureTimeMillis
fun main(args: Array<String>) {
var sink = 0;
repeat(5) {
println(measureTimeMillis {
var sum = 0
for (i in 1..10_000_000) {
sum += i
}
sink += sum
})
}
repeat(5) {
println(measureTimeMillis {
sink += (1..10_000_000).sum()
})
}
}

令人惊讶的是,使用Iterable.sum()的速度要慢 10 倍, 与与 sum(( 实现几乎相同的代码相比。 为什么?

更新:

当我以 js 为目标时,sum(( 只是稍微慢一点。

measureTimeMillis()可以定义为:

import kotlin.js.Date
public inline fun measureTimeMillis(block: () -> Unit): Double {
val start = Date.now()
block()
return Date.now() - start
}

更新2:

在同一台 Linux 机器上,jvm sum(( 甚至比 js 慢。以下是 jvm (Oracle jdk9( 和 js(最新 chrome( 的 100_000_000 次迭代的结果:

105   // jvm raw loop
76    // jvm raw loop (jit?)
75    // jvm raw loop (jit?)
75    // jvm raw loop (jit?)
70    // jvm raw loop (jit?)
633   // jvm sum()
431   // jvm sum()
562   // jvm sum()
327   // jvm sum() (jit?)
332   // jvm sum() (jit?)
110   // js raw loop
108   // js raw loop
232   // js raw loop
227   // js raw loop
227   // js raw loop
321   // js sum()
284   // js sum()
264   // js sum()
266   // js sum()
265   // js sum()

因此,在同一台机器上,jvm在使用sum()时似乎比js慢。又是一个惊喜。

显然,我们在这里比较的是超优化的紧密循环。在"内置"情况下,我在"手动总和"和巨大方差的重复中看到相当稳定的结果。这表示气相色谱活性。

在启动 VisualVM 并使用其 VisualGC 插件时,我确认在手动求和计算过程中没有 GC 活动,但在内置案例中

有很多活动。查看生成的字节码,差异变得明显:成语for (i in 1..range) { ... }直接编译成计数循环。这实际上是有记录的:

积分类型范围(IntRangeLongRangeCharRange(有一个额外的特性:它们可以迭代。编译器负责将其类似地转换为 Java 的索引 for 循环,而不会产生额外的开销。

不幸的是,相同的优化不适用于扩展函数Iterable.sum()因为它必须适用于任何Iterable。编译器可以看到发生了什么,并引入另一个内联函数,它会简单地将整个事情转换为结果总和而无需计算,或者如果范围边界没有硬编码,则使用直接公式。

JavaScript在这里也处于类似的立场,因为它也有一个强大的JIT编译器。我不能评论任何具体的东西,但它很可能避免了热循环中的分配。

相关内容

  • 没有找到相关文章

最新更新