最小化Java函数调用开销

时间:2014-07-21 19:21:00

标签: java performance optimization java-8 jmh

我有a piece of code出现在我运行的每个测试中,函数调用会产生大量开销。代码是一个紧密循环,对数组的每个元素执行一个非常简单的函数(包含4-8百万int s)。

运行代码,主要由

组成
for (int y = 1; y < h; ++y) {
    for (int x = 1; x < w; ++x) {
        final int p = y * s + x;
        n[p] = f.apply(d, s, x, y);
    }
}

执行类似

的操作
(final int[] d, final int s, final int x, final int y) -> {
    final int p = s * y + x;
    final int a = d[p] * 2
                + d[p - 1]
                + d[p + 1]
                + d[p - s]
                + d[p + s];
    return (1000 * (a + 500)) / 6000;
};

在各种机器上(我的工作笔记本电脑,带有i7 3840QM的W530,带有Xeon E5-1620核心的服务器VM,以及带有一个未知CPU核心的数字海洋节点),我反复获得统计上的显着性调用方法vs内联时性能上升。所有测试均在Java 1.8.0_11(Java HotSpot(TM)64位服务器VM)上执行。

工作机器:

Benchmark                               Mode   Samples        Score  Score error    Units
c.s.q.ShaderBench.testProcessInline    thrpt       200       40.860        0.184    ops/s
c.s.q.ShaderBench.testProcessLambda    thrpt       200       22.603        0.159    ops/s
c.s.q.ShaderBench.testProcessProc      thrpt       200       22.792        0.117    ops/s

专用服务器,VM:

Benchmark                               Mode   Samples        Score  Score error    Units
c.s.q.ShaderBench.testProcessInline    thrpt       200       40.685        0.224    ops/s
c.s.q.ShaderBench.testProcessLambda    thrpt       200       16.077        0.113    ops/s
c.s.q.ShaderBench.testProcessProc      thrpt       200       23.827        0.088    ops/s

DO VPS:

Benchmark                               Mode   Samples        Score  Score error    Units
c.s.q.ShaderBench.testProcessInline    thrpt       200       24.425        0.506    ops/s
c.s.q.ShaderBench.testProcessLambda    thrpt       200        9.643        0.140    ops/s
c.s.q.ShaderBench.testProcessProc      thrpt       200       13.733        0.134    ops/s

所有可接受的性能,但我有兴趣弄清楚为什么呼叫有如此大的开销,以及可以做些什么来优化它。目前正在尝试不同的参数集。

列出所有潜在的操作将是困难的,但理论上是可行的。对于接近2倍的性能提升,可能值得,但维护将是一场噩梦。

我不确定是否有合理的方法来批量重复一组;大多数操作都需要多个输入(调用者不知道)并产生单个输出。

我还有哪些其他方法可以减少开销和夜间表现?

1 个答案:

答案 0 :(得分:9)

方法调用不是问题,因为热方法经常被内联。 虚拟电话是一个问题。

在您的代码中,类型分析器被初始化方法Image.random欺骗。当Image.process第一次进行JIT编译时,它会针对调用random.nextInt()进行优化。因此,Image.process的下一次调用将导致内联缓存未命中,然后是对Shader.apply的非优化虚拟调用。

  1. 从初始化方法中删除Image.process调用,然后JIT将内联对Shader.apply的有用调用。

  2. 内联BlurShader.apply后,您可以通过替换

    帮助JIT执行Common subexpression elimination优化
    final int p = s * y + x;
    

    final int p = y * s + x;
    

    后一个表达式也在Image.process中得到满足,因此JIT不会两次计算相同的表达式。

  3. 应用这两项变更后,我已达到理想的基准分数:

    Benchmark                           Mode   Samples         Mean   Mean error    Units
    s.ShaderBench.testProcessInline    thrpt         5       36,483        1,255    ops/s
    s.ShaderBench.testProcessLambda    thrpt         5       36,323        0,936    ops/s
    s.ShaderBench.testProcessProc      thrpt         5       36,163        1,421    ops/s