3

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

运行代码,主要包括

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 核心的 Digital Ocean 节点),当调用方法与内联。所有测试均在 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

专用服务器,虚拟机:

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

做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 倍的性能提升,这可能是值得的,但维护将是一场噩梦。

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

我还有哪些其他选择可以最小化开销和平衡性能?

4

1 回答 1

9

方法调用不是问题,因为热方法通常是内联的。虚拟通话是个问题。

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

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

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

    final int p = s * y + x;
    

    final int p = y * s + x;
    

    后一个表达式也在 中遇到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
于 2014-07-21T21:40:43.207 回答