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() implementation相同的代码相比.
这是为什么?
更新:
当我以js为目标时,sum()只会稍慢一些.
measureTimeMillis()可以定义为:
import kotlin.js.Date public inline fun measureTimeMillis(block: () -> Unit): Double { val start = Date.now() block() return Date.now() - start }
UPDATE2:
在同一台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()
因此,在同一台机器上,当使用sum()时,jvm似乎比js慢.又一个惊喜.
显然,我们在这里比较超优化的紧密循环.对于“内置”案例中的“手动求和”和狂野差异,我看到了相当稳定的结果.这表明GC活动.在启动VisualVM并使用其VisualGC插件时,我确认在手动求和计算期间没有GC活动,但在内置的情况下有很多.
查看生成的字节码,差异变得明显:(i in 1..range){…}的习语直接编译成计数循环.这实际上是documented:
Integral type ranges (
IntRange
,LongRange
,CharRange
) have an extra feature: they can be iterated over. The compiler takes care of converting this analogously to Java’s indexed for-loop, without extra overhead.
不幸的是,相同的优化不适用于扩展函数Iterable.sum(),因为它必须适用于任何Iterable.编译器可以看到正在发生的事情并引入另一个内在函数,它只是简单地将整个事物转换为结果总和而不进行计算,或者如果范围界限不是硬编码则使用直接公式.
JavaScript在这里也有类似的基础,因为它也有一个强大的JIT编译器.我无法评论任何具体的内容,但它最有可能避免在热循环中进行分配.