寻找有关如何在特定测试中提高groovy性能的想法

时间:2015-04-05 18:29:38

标签: java performance groovy

在将现有批处理流程从Java转换为Groovy时,我遇到了一些相当重要的性能问题。用Java编写的现有批处理过程定期读取来自不同数据源的数据并执行一些数据转换。已发现的是,在将Java代码转换为Groovy后,性能显着降低,出现意外的高差距10倍以上。

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 }

和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);
        }
    }

Groovy测试结果为342毫秒,Java测试结果为30毫秒:

-------------------------------------------------------
 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

如果您对如何提高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

这是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)

Groovy版本为2.3.7 pom.xml中定义的Groovy

更新。

  1. List items = (0..length) List even = items.findAll { int item -> item > 0 && item % 2 == 0 } 代码建议修改:

    warm up
  2. 重复测试方法调用./speed-test.sh测试

  3. 我运行了groovy,它分别运行javajvm个测试。 /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 的启动从未包含在测试中。

    以下是我能够在同一个jvm进程中运行相同方法10次以获得预热的最佳结果:

    Groovy

    正如@blackdrag指出的那样,feature/option-1需要更长的时间来预热。在预热循环之后,执行仍然需要约5倍(即使排除了初始预热循环)。如果有人想要查看,则更新的代码位于分支{{1}}上。

1 个答案:

答案 0 :(得分:6)

我大致有一些性能测试指南:

  • 确保测量值超过1秒,以避免计算机时钟发生计时错误。
  • 始终提供Groovy和Java版本以及计算机规范以便能够比较事情
  • 总是有足够长的热身阶段
  • 不要一起运行多个微基准
  • 测量多次迭代以获得平均值优先于测量单次迭代。

由于性能测试是一个非常广泛的领域,尤其是微基准测试(因为您可能无法测试您认为测试的内容)。我也为你的案例提供了一些提示,但是对于这个平台来说,进入所有细节可能太过分了。

首先,您应该考虑要测试的内容。它是峰值性能,平均性能还是初始性能?有没有启动成本?您可能知道JVM使用部分解释的和部分运行时编译的代码。何时以及如何将解释的代码转换为编译代码取决于例如迭代次数,调用包含代码的方法(以及使用的类型,代码大小和许多其他内容)

如果你达到最佳表现,那么junit不是正确的工具。例如JMH在这里会更好,因为它不仅可以处理预热时间,还可以在稳定阶段停止。

例如,在第一次使用groovy运行时时会执行很多类加载,其中加载了默认的groovy方法。仅此一项就可以轻松占用您观察到的一半时间,实际上此时尚未执行任何代码。

@CompileStatic可以提供帮助,但我们还不能总是阻止加载groovy元类系统。因此即使这样,也可能有这种预热成本。更不用说JVM本身就有预热成本。

我的计算机上的原始代码需要大约752ms的原始代码。只添加一次预热的预热,这可以降低到14-20ms。

还有一些逻辑断开连接...... List items = (0..length).collect()范围已经是一个列表,所以不需要在这里调用collect。这只会通过复制每个元素来生成一个新列表。而collect()不会将元素转换为long。由于我们处理的是Integer对象,因此无需通过调用longValue()将其转换为long。仅纠正这两件事就已经将执行时间减少到一半(至少在我的电脑上,没有预热阶段)。但是热身阶段在这里真的有所作为。因此,通过预热和那些修正,我已达到10ms(50k元素)。为了比较它,Java版本需要5ms。我发现它已经很短暂来测试它。因此,如果我用100万个元素重做测试,我会看到73ms(Java)与200ms(Groovy)。当然我也改变了Java版本以使用Integer。

添加类型提示以启用原语优化List even = items.findAll {int item -> item > 0 && item % 2 == 0 }可将性能提升至约120毫秒。

在其他情况下@CompileStatic或使用invokedynamic运行(invokedynamic版本的性能在很大程度上取决于JVM版本!)也可能有助于提高性能。在我假设的这个测试中,他们不会做太多。