Kotlin 中的 forEach 范围较慢

Slow range forEach in Kotlin

我使用以下代码来测量 Kotlin 中不同语法结构的性能

fun time(what: String, body: () -> Int) {
    val start = System.currentTimeMillis()
    var sum = 0

    repeat(10) {
        sum += body()
    }

    val end = System.currentTimeMillis()

    println("$what: ${(end - start) / 10}")
}

val n = 200000000
val rand = Random()
val arr = IntArray(n) { rand.nextInt() }

time("for in range") {
    var sum = 0
    for (i in (0 until n))
        sum += arr[i]
    sum
}

time("for in collection") {
    var sum = 0
    for (x in arr)
        sum += x
    sum
}

time("forEach") {
    var sum = 0
    arr.forEach { sum += it }
    sum
}

time("range forEach") {
    var sum = 0
    (0 until n).forEach { sum += arr[it] }
    sum
}

time("sum") {
    arr.sum()
}

这就是我得到的结果:

for in range: 84
for in collection: 83
forEach: 86
range forEach: 294
sum: 83

所以我的问题是:为什么 range forEach 比其他语法结构慢得多?
在我看来,编译器可能会在所有情况下生成相同的字节码(但在 "range forEach" 的情况下不会)

最有趣的比较可能是这两种情况:

案例 A:耗时 86 毫秒

time("forEach") {
    var sum = 0
    arr.forEach { sum += it }
    sum
}

案例 B:耗时 294 毫秒

time("range forEach") {
    var sum = 0
    (0 until n).forEach { sum += arr[it] }
    sum
}

虽然案例 A 实际上正在调用 IntArray.forEach(...) 案例 B 正在调用 Iterable<T>.forEach(...) - 这是两个不同的调用。

我猜 Kotlin 编译器知道优化 IntArray.forEach(...),但不知道 Iterable<T>.forEach(...)

来自文档:

A for loop over a range or an array is compiled to an index-based loop that does not create an iterator object.

forEach 正如您在 https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/for-each.html 看到的那样,它是数组的特殊情况,但对所有 Iterable 有一个单一的实现,因此它需要创建一个迭代器。

感谢您提供此代码。我对数组和列表之间的性能差异更感兴趣,并且 运行 的时间是原来的 6 倍,这让我感到有些震惊。我以为这个列表只是创建速度慢。

我机器上的以上运行时间用于比较:

范围内:55
因为 collection: 57
对于每个:55
每个范围:223
总和:57

//code change replace IntArray with List
//val arr = IntArray(n) { rand.nextInt() }
val arr = List(n) { rand.nextInt() }

范围内:383
因为 collection: 367
对于每个:367
每个范围:486
总和:371

//code change replace IntArray with Array<Int>
//val arr = IntArray(n) { rand.nextInt() }
val arr: Array<Int> = Array<Int>(n) { rand.nextInt() }

在范围内:331
对于 collection: 281
对于每个:276
每个范围:445
总和:313

因此,主要区别不在于使用 Array 与 List,而是使用 Object Int 与原始类型