最小化Java函数调用开销

ssu*_*ube 3 java optimization performance java-8 jmh

我有一段代码,在我运行的每个测试中,函数调用都会产生大量的开销.代码是一个紧密的循环,在一个数组的每个元素上执行一个非常简单的函数(包含4-8百万ints).

运行主要由以下内容组成的代码

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

执行类似的事情

(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;
};
Run Code Online (Sandbox Code Playgroud)

在各种机器上(我的工作笔记本电脑,带有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
Run Code Online (Sandbox Code Playgroud)

专用服务器,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
Run Code Online (Sandbox Code Playgroud)

做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
Run Code Online (Sandbox Code Playgroud)

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

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

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

我还有哪些其他选项可以减少开销和夜间性能?

apa*_*gin 9

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

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

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

  2. BlurShader.apply在内联之后,您可以帮助JIT 通过替换来执行Common子表达式消除优化

    final int p = s * y + x;
    
    Run Code Online (Sandbox Code Playgroud)

    同

    final int p = y * s + x;
    
    Run Code Online (Sandbox Code Playgroud)

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

应用这两个变化后,我达到了理想的基准分数:

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
Run Code Online (Sandbox Code Playgroud)