我遇到了一些相当显著性能问题,同时从转换现有的批量处理Java到Groovy.写入的现有批处理过程Java定期读取来自不同数据源的数据并执行一些数据转换.已经发现的是一个显着的性能下降,在将Java代码转换为代码后出现意外的高10+倍差距Groovy.
https://github.com/nicolas-martinez/grava-speed-test上的代码是一个简化示例,它显示了使用简单循环发现的问题之一以及使用集合闭包进行过滤.它被设置为Maven项目,可以在本地轻松克隆并执行.
以下是Groovy代码的重点:
List items = (0..length).collect()
List even = items.findAll { item -> item > 0 && item.longValue() % 2 == 0 }
Run Code Online (Sandbox Code Playgroud)
和Java代码:
List<Long> items = new ArrayList(length);
for (int i = 0; i < length; i++) {
items.add(Long.valueOf(i + 1));
}
List<Long> even = new ArrayList<Long>();
for(Long item : items){
if (item > 0 && item % 2 == 0) {
even.add(item);
}
}
Run Code Online (Sandbox Code Playgroud)
测试结果为342 Groovy毫秒,30毫秒以下Java:
-------------------------------------------------------
T E S T S
-------------------------------------------------------
Running tst.speedtest.GroovyFilterTest
testFilter: 500000 elapsed: 342
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.637 sec
Running tst.speedtest.JavaFilterTest
testFilterUsingInterface: 500000 elapsed: 29
testFilter: 500000 elapsed: 27
Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.048 sec
Run Code Online (Sandbox Code Playgroud)
如果您对如何提高Groovy性能有建议,请告诉我.我们的团队正在考虑迁移到Groovy,因为它提供了一些先进的功能,但由于迄今为止我们遇到的性能差距很大,因此很难证明这一点.
以下是我报告的硬件配置文件system_profiler SPHardwareDataType:
Hardware Overview:
Model Name: MacBook Pro
Model Identifier: MacBookPro11,3
Processor Name: Intel Core i7
Processor Speed: 2.5 GHz
Number of Processors: 1
Total Number of Cores: 4
L2 Cache (per Core): 256 KB
L3 Cache: 6 MB
Memory: 16 GB
Boot ROM Version: MBP112.0138.B11
SMC Version (system): 2.19f12
Run Code Online (Sandbox Code Playgroud)
这是Java版本:
java version "1.7.0_72"
Java(TM) SE Runtime Environment (build 1.7.0_72-b14)
Java HotSpot(TM) 64-Bit Server VM (build 24.72-b04, mixed mode)
Run Code Online (Sandbox Code Playgroud)
该Groovy版本2.3.7中定义的pom.xml.
UPDATE.
建议修改Groovy代码:
List items = (0..length)
List even = items.findAll { int item -> item > 0 && item % 2 == 0 }
Run Code Online (Sandbox Code Playgroud)加入试验方法调用的重复来warm up测试
我分别跑了./speed-test.sh哪个跑步groovy和java测试.启动jvm时从未包含在测试中.
以下是我能够在同一个jvm进程中运行相同方法10次以获得预热的最佳结果:
/speed-test.sh
Java test
Java testUsingInterface: 500000 elapsed: 44
Java testUsingInterface: 500000 elapsed: 43
Java testUsingInterface: 500000 elapsed: 28
Java testUsingInterface: 500000 elapsed: 11
Java testUsingInterface: 500000 elapsed: 31
Java testUsingInterface: 500000 elapsed: 10
Java testUsingInterface: 500000 elapsed: 9
Java testUsingInterface: 500000 elapsed: 11
Java testUsingInterface: 500000 elapsed: 19
Java testUsingInterface: 500000 elapsed: 19
JavaTest: for testSize=1000000 and repeat=10 total elapsed: 226
Groovy Test
GroovyTest: 500000 elapsed: 199
GroovyTest: 500000 elapsed: 76
GroovyTest: 500000 elapsed: 91
GroovyTest: 500000 elapsed: 80
GroovyTest: 500000 elapsed: 58
GroovyTest: 500000 elapsed: 83
GroovyTest: 500000 elapsed: 91
GroovyTest: 500000 elapsed: 58
GroovyTest: 500000 elapsed: 58
GroovyTest: 500000 elapsed: 67
GroovyTest: for testSize=1000000 and repeat=10 total elapsed: 1073
Run Code Online (Sandbox Code Playgroud)
正如@blackdrag指出的那样,Groovy需要更长的时间来预热.在预热循环之后,执行仍然需要约5倍(即使排除了初始预热循环).feature/option-1如果有人想要查看,更新的代码在分支上.
我总体上大致有一些性能测试指南:
由于性能测试是一个非常广泛的领域,尤其是微基准测试(因为您可能无法测试您认为测试的内容).我也为你的案例提供了一些提示,但是对于这个平台来说,进入所有细节可能太过分了.
首先,您应该考虑要测试的内容.它是峰值性能,平均性能还是初始性能?有没有启动成本?您可能知道JVM使用部分解释的和部分运行时编译的代码.何时以及如何将解释的代码转换为编译代码取决于例如迭代次数,调用包含代码的方法(以及使用的类型,代码大小和许多其他东西)
如果你达到最佳性能,那么junit不是正确的工具.例如JMH在这里会更好,因为它不仅可以处理预热时间,还可以在稳定阶段停止.
例如,在第一次使用groovy运行时时会执行大量的类加载,其中加载了默认的groovy方法.仅此一项就可以轻松占用您观察到的一半时间,实际上此时尚未执行任何代码.
@CompileStatic可以提供帮助,但我们还不能总是阻止加载groovy元类系统.因此即使这样,也可能有这种预热成本.更不用说JVM本身就有预热成本.
我的计算机上的原始代码需要大约752ms的原始代码.仅添加一次预热的预热就可以达到14-20ms.
并且还有一些逻辑断开连接...... List items = (0..length).collect()范围已经是一个列表,所以不需要在这里调用collect.这只会通过复制每个元素来生成一个新列表.并且collect()不会将元素转换为long.由于我们处理的是Integer对象,因此无需通过调用转换为long longValue().仅纠正这两件事就已经将执行时间减少到一半(至少在我的电脑上,没有预热阶段).但是热身阶段在这里真的有所作为.因此,通过预热和那些修正,我已达到10ms(50k元素).为了比较它,Java版本需要5ms.我发现它已经很短暂来测试它.因此,如果我用100万个元素重做测试,我会看到73ms(Java)与200ms(Groovy).当然我也改变了Java版本以使用Integer.
添加类型提示以启用原语优化List even = items.findAll {int item -> item > 0 && item % 2 == 0 }将性能提高到大约120ms.
在其他情况下@CompileStatic或使用invokedynamic运行(invokedynamic版本的性能在很大程度上取决于JVM版本!)也可能有助于提高性能.在我假设的这个测试中,他们不会做太多.
| 归档时间: |
|
| 查看次数: |
742 次 |
| 最近记录: |