【发布时间】:2014-05-23 11:46:43
【问题描述】:
几个小时前,我回答了另一个 StackOverflow 问题,结果令人惊讶。答案可以在here找到。答案是/部分错误,但我觉得专注于字节添加。
严格来说,实际上是字节到长的加法。
这是我一直在使用的基准代码:
public class ByteAdditionBenchmark {
private void start() {
int[] sizes = {
700_000,
1_000,
10_000,
25_000,
50_000,
100_000,
200_000,
300_000,
400_000,
500_000,
600_000,
700_000,
};
for (int size : sizes) {
List<byte[]> arrays = createByteArrays(size);
//Warmup
arrays.forEach(this::byteArrayCheck);
benchmark(arrays, this::byteArrayCheck, "byteArrayCheck");
}
}
private void benchmark(final List<byte[]> arrays, final Consumer<byte[]> method, final String name) {
long start = System.nanoTime();
arrays.forEach(method);
long end = System.nanoTime();
double nanosecondsPerIteration = (end - start) * 1d / arrays.size();
System.out.println("Benchmark: " + name + " / iterations: " + arrays.size() + " / time per iteration: " + nanosecondsPerIteration + " ns");
}
private List<byte[]> createByteArrays(final int amount) {
Random random = new Random();
List<byte[]> resultList = new ArrayList<>();
for (int i = 0; i < amount; i++) {
byte[] byteArray = new byte[4096];
byteArray[random.nextInt(4096)] = 1;
resultList.add(byteArray);
}
return resultList;
}
private boolean byteArrayCheck(final byte[] array) {
long sum = 0L;
for (byte b : array) {
sum += b;
}
return (sum == 0);
}
public static void main(String[] args) {
new ByteAdditionBenchmark().start();
}
}
这是我得到的结果:
基准测试:byteArrayCheck / 迭代次数:700000 / 每次迭代的时间:50.26538857142857 ns
基准测试:byteArrayCheck / 迭代次数:1000 / 每次迭代时间:20.12 ns
基准测试:byteArrayCheck / 迭代次数:10000 / 每次迭代的时间:9.1289 ns
基准测试:byteArrayCheck / 迭代次数:25000 / 每次迭代的时间:10.02972 ns
基准测试:byteArrayCheck / 迭代次数:50000 / 每次迭代的时间:9.04478 ns
基准测试:byteArrayCheck / 迭代次数:100000 / 每次迭代的时间:18.44992 ns
基准测试:byteArrayCheck / 迭代次数:200000 / 每次迭代的时间:15.48304 ns
基准测试:byteArrayCheck / 迭代次数:300000 / 每次迭代的时间:15.806353333333334 ns
基准测试:byteArrayCheck / 迭代次数:400000 / 每次迭代的时间:16.923685 ns
基准测试:byteArrayCheck / 迭代次数:500000 / 每次迭代时间:16.131066 ns
基准:byteArrayCheck / 迭代:600000 / 每次迭代的时间:16.435461666666665 ns
基准测试:byteArrayCheck / 迭代次数:700000 / 每次迭代的时间:17.107615714285714 ns
据我所知,在前 700000 次迭代之后,JVM 已经完全预热,然后才开始输出基准测试数据。
那怎么可能,尽管热身,性能仍然无法预测?几乎直接在预热字节添加之后变得非常快,但在那之后它似乎再次收敛到每次添加的标称 16 ns。
测试是在配备 Intel i7 3770 主频和 16 GB RAM 的 PC 上运行的,因此我的迭代次数不能超过 700000 次。如果重要的话,它在 Windows 8.1 64 位上运行。
事实证明,JIT 正在优化所有内容,正如 raphw's suggestion 所述。
因此,我将基准方法替换为以下内容:
private void benchmark(final List<byte[]> arrays, final Predicate<byte[]> method, final String name) {
long start = System.nanoTime();
boolean someUnrelatedResult = false;
for (byte[] array : arrays) {
someUnrelatedResult |= method.test(array);
}
long end = System.nanoTime();
double nanosecondsPerIteration = (end - start) * 1d / arrays.size();
System.out.println("Result: " + someUnrelatedResult);
System.out.println("Benchmark: " + name + " / iterations: " + arrays.size() + " / time per iteration: " + nanosecondsPerIteration + "ns");
}
这将确保它不能被优化掉并且测试结果也会显示它(为了清楚起见省略了结果打印):
基准:byteArrayCheck / 迭代:700000 / 每次迭代的时间:1658.2627914285715 ns
基准测试:byteArrayCheck / 迭代次数:1000 / 每次迭代的时间:1241.706 ns
基准测试:byteArrayCheck / 迭代次数:10000 / 每次迭代的时间:1215.941 ns
基准测试:byteArrayCheck / 迭代次数:25000 / 每次迭代的时间:1332.94656 ns
基准测试:byteArrayCheck / 迭代次数:50000 / 每次迭代的时间:1456.0361 ns
基准测试:byteArrayCheck / 迭代次数:100000 / 每次迭代的时间:1753.26777 ns
基准测试:byteArrayCheck / 迭代次数:200000 / 每次迭代的时间:1756.93283 ns
基准测试:byteArrayCheck / 迭代次数:300000 / 每次迭代的时间:1762.9992266666666 ns
基准测试:byteArrayCheck / 迭代次数:400000 / 每次迭代的时间:1806.854815 ns
基准测试:byteArrayCheck / 迭代次数:500000 / 每次迭代的时间:1784.09091 ns
基准测试:byteArrayCheck / 迭代次数:600000 / 每次迭代的时间:1804.6096366666666 ns
基准:byteArrayCheck / 迭代:700000 / 每次迭代的时间:1811.0597585714286 ns
我想说,这些结果在计算时间方面看起来更有说服力。但是,我的问题仍然存在。在随机时间重复测试时,相同的模式仍然是,迭代次数少的基准测试比迭代次数多的基准测试更快,尽管它们似乎稳定在 100,000 次迭代或更低的某个地方。
解释是什么?
【问题讨论】:
-
您正在为每一轮基准测试创建新数组。因此垃圾收集器可能会对结果产生影响。
-
@Robert 不幸的是,我不能很好地纠正它,因为我没有空间将所有内容存储在内存中。并且遗漏一些数据可能会影响测试。
-
@skiwi 如果你打电话给
System.gc()会发生什么?我知道它不能保证 gc,但是从 Jon Skeet 的观察之一(找不到问题......)无论如何它几乎总是发生。你也应该能够设置一个 JVM 标志,当 gc 发生时打印出来 -
使用适当的基准测试框架来防止大量不确定的事情,或者这根本是无效或严重的。从调查 JMH 或 Caliper 开始。
-
你有一个 64kbyte L1 缓存,从外观上看,这是造成差异的原因。
标签: java benchmarking