在Groovy中迭代Collection对象的最佳和最快方法是什么。我知道有几种Groovy收集实用程序方法。但是他们使用的是缓慢的闭包。
答案 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
for (int i = 0; ...
几乎比for-for慢两倍,这很可能是因为需要付出额外的努力才能从给定索引的数组中返回值。each
方法比流+闭包变体快一点,并且与最快的相比,两者都慢两倍以上。为特定用例运行基准以获得最准确的答案很重要。例如,如果在迭代旁边应用了其他一些操作(过滤,映射等),则Stream API将是最佳选择。对于从给定集合的第一个元素到最后一个元素的简单迭代,选择旧的Java for-each可能会产生最佳结果,因为它不会产生太多开销。
也-收集的大小很重要。例如,如果使用上面的示例,而不是迭代超过1亿个元素,我们将迭代超过10万个元素,那么最慢的变量将花费0.82
毫秒与0.38
毫秒。如果构建的系统每个纳秒都很重要,那么您必须选择最有效的解决方案。但是,如果您构建一个简单的CRUD应用程序,则集合的迭代花费0.82
或0.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类,其中有0.09
上的1000万个元素用于比较:
stream().forEach()
仅比Groovy代码中的静态编译for-for快一点。