代码之家  ›  专栏  ›  技术社区  ›  Aliquis

科特林:为什么序列在这个例子中更有效?

  •  3
  • Aliquis  · 技术社区  · 7 年前

    目前,我正在调查科特林,有一个关于 序列与集合

    我读了一本书 blog post 关于此主题,您可以在那里找到以下代码片段:

    val list = generateSequence(1) { it + 1 }
        .take(50_000_000)
        .toList()
    
    measure {
        list
            .filter { it % 3 == 0 }
            .average()
    }
    // 8644 ms
    

    顺序执行:

    val sequence = generateSequence(1) { it + 1 }
        .take(50_000_000)
    
    measure {
        sequence
            .filter { it % 3 == 0 }
            .average()
    }
    // 822 ms
    

    这里的重点是序列实现大约快10倍。

    然而,我知道为什么序列通常更快:

    val result = sequenceOf("a", "b", "c")
        .map {
            println("map: $it")
            it.toUpperCase()
        }
        .any {
            println("any: $it")
            it.startsWith("B")
        }
    

    那么,为什么在第一个例子中速度更快呢?

    2 回复  |  直到 7 年前
        1
  •  10
  •   gidds    7 年前

    让我们看看这两个实现实际上在做什么:

    1. List

      (事实上,可能是 远的 不止这些。正如阿列克谢·罗曼诺夫所指出的,因为它是一个通用的 列表 实施而不是 IntList Int 物体。在JVM上,每个引用可以是8或16个字节,每个 内景 可能需要16,也就是12GB。另外,取决于 列表 创建时,它可能会从一个小数组开始,并随着列表的增长不断创建越来越大的数组,每次都复制所有值,仍然会使用更多的内存。)

      另一个 在内存中列出。

      那些 值重新输入,以计算平均值。

    2. 另一方面,序列实现不需要存储任何东西!它只是按顺序生成值 就像每一个一样 它检查它是否可以被3整除,如果可以,就把它包括在平均数中。

      (如果你是手工实现的话,你就会这么做。)

    你可以看到除了整除性检查和平均值计算, 这就是它比序列版本慢得多的主要原因,序列版本没有!

        2
  •  4
  •   Grzegorz Piwowarek    7 年前

    利用延迟评估可以避免创建与最终目标点无关的中间对象。

    此外,本文中使用的基准测试方法也不是超精确的。试着重复这个实验 JMH

    初始代码生成一个包含50000个对象的列表:

     val list = generateSequence(1) { it + 1 }
      .take(50_000_000)
      .toList()
    

    创建另一个列表 包含其元素的子集:

    .filter { it % 3 == 0 }
    

    .average()
    

    val sequence = generateSequence(1) { it + 1 }
      .take(50_000_000)
    

    .filter{it%3==0}
    

    最终,一个终端操作被调用,它触发对序列和实际计算的评估:

    .平均()
    

    相关阅读:

    Kotlin: Beware of Java Stream API Habits

    Kotlin Collections API Performance Antipatterns