gpt4 book ai didi

仿函数上的 Java8 热点

转载 作者:行者123 更新时间:2023-12-04 15:50:59 25 4
gpt4 key购买 nike

以下代码在 Java8 上的性能比较是违反直觉的。

import java.util.Arrays;

class Main {
interface Dgemv {
void dgemv(int n, double[] a, double[] x, double[] y);
}

static final class Dgemv1 implements Dgemv {
public void dgemv(int n, double[] a, double[] x, double[] y) {
Arrays.fill(y, 0.0);
for (int j = 0; j < n; ++j)
dgemvImpl(x[j], j * n, n, a, y);
}

private void dgemvImpl(final double xj, final int aoff,
final int n, double[] a, double[] y) {
for (int i = 0; i < n; ++i)
y[i] += xj * a[i + aoff];
}
}

static final class Dgemv2 implements Dgemv {
public void dgemv(int n, double[] a, double[] x, double[] y) {
Arrays.fill(y, 0.0);
for (int j = 0; j < n; ++j)
new DgemvImpl(x[j], j * n).dgemvImpl(n, a, y);
}

private static final class DgemvImpl {
private final double xj;
private final int aoff;

DgemvImpl(double xj, int aoff) {
this.xj = xj;
this.aoff = aoff;
}

void dgemvImpl(final int n, double[] a, double[] y) {
for (int i = 0; i < n; ++i)
y[i] += xj * a[i + aoff];
}
}
}

static long runDgemv(long niter, int n, Dgemv op) {
double[] a = new double[n * n];
double[] x = new double[n];
double[] y = new double[n];
long start = System.currentTimeMillis();
for (long i = 0; i < niter; ++i) {
op.dgemv(n, a, x, y);
}
return System.currentTimeMillis() - start;
}

static void testDgemv(long niter, int n, int mode) {
Dgemv op = null;
switch (mode) {
case 1: op = new Dgemv1(); break;
case 2: op = new Dgemv2(); break;
}
runDgemv(niter, n, op);
double sec = runDgemv(niter, n, op) * 1e-3;
double gflps = (2.0 * n * n) / sec * niter * 1e-9;
System.out.format("mode=%d,N=%d,%f sec,%f GFLPS\n", mode, n, sec, gflps);
}

public static void main(String[] args) {
int n = Integer.parseInt(args[0]);
long niter = ((long) 1L << 32) / (long) (2 * n * n);
testDgemv(niter, n, 1);
testDgemv(niter, n, 2);
}
}

在 Java8 (1.8.0_60) 和 Core i5 4570 (3.2GHz) 上的结果是:

$ java -server Main
mode=1,N=500,1.239000 sec,3.466102 GFLPS
mode=2,N=500,1.100000 sec,3.904091 GFLPS

在Java7(1.7.0_80)上同样的计算结果是:

mode=1,N=500,1.291000 sec,3.326491 GFLPS
mode=2,N=500,1.491000 sec,2.880282 GFLPS

似乎 HotSpot 比静态方法更热切地优化仿函数,不管额外的复杂性如何。

谁能解释为什么 Dgemv2 运行得更快?

编辑:

来自 openjdk/jmh 的更精确的基准统计数据. (感谢 Kayaman 的评论)

N=500/1 秒 x 20 次热身/1 秒 x 20 次迭代(10 组)

Java 8 (1.8.0_60)

Benchmark               Mode  Cnt     Score   Error  Units
MyBenchmark.runDgemv1 thrpt 200 6965.459 ? 2.186 ops/s
MyBenchmark.runDgemv2 thrpt 200 7329.138 ? 1.598 ops/s

Java 7 (1.7.0_80)

Benchmark               Mode  Cnt     Score   Error  Units
MyBenchmark.runDgemv1 thrpt 200 7344.570 ? 1.994 ops/s
MyBenchmark.runDgemv2 thrpt 200 7358.988 ? 2.189 ops/s

从这些统计数据来看,Java 8 HotSpot 似乎没有优化静态方法。但我注意到的另一件事是,某些热身部分的性能提高了 10%。拾取极端情况:

N=500/1 秒 x 8 次热身/1 秒 x 8 次迭代(10 组)

Java 8 (1.8.0_60)

Benchmark               Mode  Cnt     Score    Error  Units
MyBenchmark.runDgemv1 thrpt 80 6952.315 ? 11.483 ops/s
MyBenchmark.runDgemv2 thrpt 80 7719.843 ? 66.773 ops/s

Dgemv2 在 9 秒到 15 秒之间的迭代始终比长期平均值高出约 5%。随着优化过程的进行,HotSpot 似乎并不总是产生更快的代码。

我目前的猜测是 Dgemv2 中的 Functor 对象实际上扰乱了 HotSpot 优化过程,导致执行代码比“完全优化代码”更快。

我仍然不清楚为什么会发生这种情况。欢迎任何答案和评论。

最佳答案

如apangin的评论,来自HotSpot的loop unrolling optimization。

采用相同的基准测试,通过 jvm 的选项 '-XX:LoopUnrollLimit=??' 更改 LoopUnrollLimit 参数(64 位 x86 中的默认值=60),上述基准测试中的优化似乎处于循环展开决策的边缘。

Java 8 (1.8.0_60)

LoopUnrollLimit |       30|       60|       90|      120|      240|
-------------------------------------------------------------------
Dgemv1 (ops/s) | 6322.156| 6967.511| 7632.563| 8811.307| 8811.552|
Dgemv2 (ops/s) | 5631.505| 7328.529| 7689.380| 8774.118| 8780.631|

Java 7 (1.7.0_80)

LoopUnrollLimit |       30|       60|       90|      120|      240|
-------------------------------------------------------------------
Dgemv1 (ops/s) | 6309.920| 7345.467| 7611.573| 8805.658| 8795.785|
Dgemv2 (ops/s) | 5632.850| 7345.467| 7658.165| 8758.698| 8762.981|

从 openjdk 的热点源代码中,将 LoopUnrollLimit 参数与节点列表的大小进行比较,这是目标循环内执行代码的内部表示(如 here )。

考虑到jvm7和jvm8在full optimization(>=120)的情况下没有区别,可能是因为jdk8改变了内部代码的发出方式,可能会扩大static method的node list围绕“LoopUnrollLimit=60”。

关于仿函数上的 Java8 热点,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/32226290/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com