【问题标题】:Array/List iteration without extra object allocations无需额外对象分配的数组/列表迭代
【发布时间】:2016-06-03 08:01:45
【问题描述】:

我正在开发一个用 Kotlin 编写的游戏,并且正在研究如何改善 GC 流失率。流失的主要来源之一是在主游戏/渲染循环中调用的 for 循环,导致分配迭代器。

翻到文档,我找到了这一段:

数组上的 for 循环被编译为基于索引的循环,不会创建迭代器对象。

如果你想遍历一个带有索引的数组或列表,你可以这样做:

for (i in array.indices)
  print(array[i])

请注意,这种“遍历范围”会被编译为最佳实现,而不会创建额外的对象。

https://kotlinlang.org/docs/reference/control-flow.html#for-loops

这是真的吗?为了验证,我使用了这个简单的 Kotlin 程序并检查了生成的字节码:

fun main(args: Array<String>) {
    val arr = arrayOf(1, 2, 3)
    for (i in arr.indices) {
        println(arr[i])
    }
}

根据上面的引用,这不应该导致任何对象被分配,而是被编译成一个很好的旧 Java-5 前风格的 for 循环。然而,我得到的是这样的:

      41: aload_1
      42: checkcast     #23                 // class "[Ljava/lang/Object;"
      45: invokestatic  #31                 // Method kotlin/collections/ArraysKt.getIndices:([Ljava/lang/Object;)Lkotlin/ranges/IntRange;
      48: dup
      49: invokevirtual #37                 // Method kotlin/ranges/IntRange.getFirst:()I
      52: istore_2
      53: invokevirtual #40                 // Method kotlin/ranges/IntRange.getLast:()I
      56: istore_3
      57: iload_2
      58: iload_3
      59: if_icmpgt     93

在我看来,这就像调用了一个名为 getIndices 的方法,它分配了一个临时的 IntRange 对象来备份此循环中的边界检查。这是一个“没有创建额外对象”的“最佳实现”,还是我遗漏了什么?

更新: 因此,在多玩了一会儿并查看了答案之后,以下内容似乎适用于 Kotlin 1.0.2:

数组:

  • for (i in array.indices):范围分配
  • for (i in 0..array.size):没有分配
  • for (el in array):没有分配
  • array.forEach:没有分配

收藏:

  • for (i in coll.indices)范围分配
  • for (i in 0..coll.size):没有分配
  • for (el in coll): 迭代器分配
  • coll.forEach: 迭代器分配

【问题讨论】:

标签: kotlin


【解决方案1】:

要在不分配额外对象的情况下迭代数组,您可以使用以下方法之一。

  1. for-loop
    for (e in arr) {
        println(e)
    }
  1. forEach分机
    arr.forEach {
        println(it)
    }
  1. forEachIndexed 扩展,如果你需要知道每个元素的索引
    arr.forEachIndexed { index, e ->
        println("$e at $index")
    }

【讨论】:

  • 现在这很有趣。我假设所有这些都会导致迭代器的分配——但它们不会,至少如果迭代对象是数组时不会。它们适用于列表和其他集合类型。感谢您指出这一点!
【解决方案2】:

据我所知,定义for 循环的唯一无分配方式是

for (i in 0..count - 1)

所有其他形式导致Range 分配或Iterator 分配。不幸的是,您甚至无法定义有效的反向 for 循环。

【讨论】:

  • 没错,这消除了IntRange 分配。这很有趣,因为它仍然明智地表示 IntRange 语法。谢谢!值得为此文档提交错误吗?这充其量只是误导。
  • 补充我之前的评论:显然编译器在起作用,因为一旦你将0..count - 1 拉出到作用于数组的函数中,它就会导致IntRange 分配。
  • 这是一个纯粹的编译器优化,我想未来还会有更多。 youtrack.jetbrains.com/issue/KT-11173
  • 在对伊利亚的回答进行了更多调查之后,我认为这不再是公认的答案了。鉴于可迭代对象是一个数组,而不是一个列表,这 3 个建议中的任何一个都不会导致额外的分配。
【解决方案3】:

这是一个准备列表并使用索引和值进行迭代的示例。

val list = arrayListOf("1", "11", "111")
for ((index, value) in list.withIndex()) {
   println("$index: $value")
}

输出:

0:1

1:11

2:111

此外,以下代码的工作方式类似,

val simplearray = arrayOf(1, 2, 3, 4, 5)
for ((index, value) in simplearray.withIndex()) {
    println("$index: $value")
}

【讨论】:

    猜你喜欢
    • 2021-02-18
    • 1970-01-01
    • 2018-10-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-04-11
    • 1970-01-01
    相关资源
    最近更新 更多