在Groovy 2.x中迭代收集对象的有效方法?

时间:2018-10-01 08:37:46

标签: groovy

在Groovy中迭代Collection对象的最佳和最快方法是什么。我知道有几种Groovy收集实用程序方法。但是他们使用的是缓慢的闭包。

1 个答案:

答案 0 :(得分:4)

在您的特定情况下,最终结果可能会有所不同,但是benchmarking有5种不同的Groovy迭代变体表明,旧的Java for-each循环是最有效的。看下面的示例,其中我们迭代了1亿个元素,并以非常必要的方式计算这些数字的总和:

@Grab(group='org.gperfutils', module='gbench', version='0.4.3-groovy-2.4')

import java.util.concurrent.atomic.AtomicLong
import java.util.function.Consumer

def numbers = (1..100_000_000)

def r = benchmark {
    'numbers.each {}' {
        final AtomicLong result = new AtomicLong()
        numbers.each { number -> result.addAndGet(number) }
    }

    'for (int i = 0 ...)' {
        final AtomicLong result = new AtomicLong()
        for (int i = 0; i < numbers.size(); i++) {
            result.addAndGet(numbers[i])
        }
    }

    'for-each' {
        final AtomicLong result = new AtomicLong()
        for (int number : numbers) {
            result.addAndGet(number)
        }
    }

    'stream + closure' {
        final AtomicLong result = new AtomicLong()
        numbers.stream().forEach { number -> result.addAndGet(number) }
    }

    'stream + anonymous class' {
        final AtomicLong result = new AtomicLong()
        numbers.stream().forEach(new Consumer<Integer>() {
            @Override
            void accept(Integer number) {
                result.addAndGet(number)
            }
        })
    }
}
r.prettyPrint()

这只是一个简单的示例,在该示例中,我们尝试对集合上的迭代成本进行基准测试,无论对集合中每个元素执行的操作是什么(所有变体都使用同一操作以提供最准确的结果)。这是结果(时间测量值以纳秒为单位):

Environment
===========
* Groovy: 2.4.12
* JVM: OpenJDK 64-Bit Server VM (25.181-b15, Oracle Corporation)
    * JRE: 1.8.0_181
    * Total Memory: 236 MB
    * Maximum Memory: 3497 MB
* OS: Linux (4.18.9-100.fc27.x86_64, amd64)

Options
=======
* Warm Up: Auto (- 60 sec)
* CPU Time Measurement: On

WARNING: Timed out waiting for "numbers.each {}" to be stable
                                user    system         cpu        real

numbers.each {}           7139971394  11352278  7151323672  7246652176
for (int i = 0 ...)       6349924690   5159703  6355084393  6447856898
for-each                  3449977333    826138  3450803471  3497716359
stream + closure          8199975894    193599  8200169493  8307968464
stream + anonymous class  3599977808   3218956  3603196764  3653224857

结论

  1. Java的for-each与Stream +匿名类一样快(Groovy 2.x不允许使用lambda表达式)。
  2. 旧的for (int i = 0; ...几乎比for-for慢两倍,这很可能是因为需要付出额外的努力才能从给定索引的数组中返回值。
  3. Groovy的each方法比流+闭包变体快一点,并且与最快的相比,两者都慢两倍以上。

为特定用例运行基准以获得最准确的答案很重要。例如,如果在迭代旁边应用了其他一些操作(过滤,映射等),则Stream API将是最佳选择。对于从给定集合的第一个元素到最后一个元素的简单迭代,选择旧的Java for-each可能会产生最佳结果,因为它不会产生太多开销。

也-收集的大小很重要。例如,如果使用上面的示例,而不是迭代超过1亿个元素,我们将迭代超过10万个元素,那么最慢的变量将花费0.82毫秒与0.38毫秒。如果构建的系统每个纳秒都很重要,那么您必须选择最有效的解决方案。但是,如果您构建一个简单的CRUD应用程序,则集合的迭代花费0.820.38毫秒都没关系-数据库连接的成本至少要大50倍,因此大约可以节省{{ 1}}毫秒不会产生任何影响。

0.44

更新:动态调用与静态编译

还有一个值得考虑的因素-静态编译。您可以在下面找到1000万个元素集合迭代基准测试的结果:

// Results for iterating over 100k elements
Environment
===========
* Groovy: 2.4.12
* JVM: OpenJDK 64-Bit Server VM (25.181-b15, Oracle Corporation)
    * JRE: 1.8.0_181
    * Total Memory: 236 MB
    * Maximum Memory: 3497 MB
* OS: Linux (4.18.9-100.fc27.x86_64, amd64)

Options
=======
* Warm Up: Auto (- 60 sec)
* CPU Time Measurement: On

                            user  system     cpu    real

numbers.each {}           717422       0  717422  722944
for (int i = 0 ...)       593016       0  593016  600860
for-each                  381976       0  381976  387252
stream + closure          811506    5884  817390  827333
stream + anonymous class  408662    1183  409845  416381

如您所见,打开静态编译(例如,带有Environment =========== * Groovy: 2.4.12 * JVM: OpenJDK 64-Bit Server VM (25.181-b15, Oracle Corporation) * JRE: 1.8.0_181 * Total Memory: 236 MB * Maximum Memory: 3497 MB * OS: Linux (4.18.10-100.fc27.x86_64, amd64) Options ======= * Warm Up: Auto (- 60 sec) * CPU Time Measurement: On user system cpu real Dynamic each {} 727357070 0 727357070 731017063 Static each {} 141425428 344969 141770397 143447395 Dynamic for-each 369991296 619640 370610936 375825211 Static for-each 92998379 27666 93026045 93904478 Dynamic for (int i = 0; ...) 679991895 1492518 681484413 690961227 Static for (int i = 0; ...) 173188913 0 173188913 175396602 类注释)可以改变游戏规则。当然,Java for-each仍然是最高效的,但是其静态变量几乎比动态变量快4倍。静态Groovy @CompileStatic比动态each {}快5倍。静态for循环也比动态for循环快4倍。

结论-对于一千万个元素,对于相同大小的集合,静态each {}花费143毫秒,而静态for-each花费93毫秒。这意味着,对于大小为100k的集合,静态numbers.each {}的花费为numbers.each {} ms,而静态for-each的花费约为0.14 ms。两者都非常快,并且真正的区别始于集合的大小爆炸到+1亿个元素。

Java编译类中的Java流

为您提供一个视角-这是Java类,其中有0.09上的1000万个元素用于比较:

stream().forEach()

仅比Groovy代码中的静态编译for-for快一点。