为什么字节加法性能如此不可预测?
几个小时前,我回答了另一个Stack Overflow问题,它给出了一个非常令人惊讶的结果。答案可以在这里找到。答案是/部分错误,但我觉得专注于字节加法。
严格来说,它实际上是字节到长的加法。
这是我一直在使用的基准代码:
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.80635333333334 ns
基准:byteArrayCheck /迭代次数:400000 / 每次迭代时间:16.923685 ns
基准:字节数组检查/迭代:500000 /每次迭代时间:16.131066 ns
基准:字节数组检查/迭代:600000 /每次迭代时间:16.43546166666666 ns
基准:字节数组检查/迭代:700000 /每次迭代时间:17.107615714285714 ns
据我所知,JVM在前700000次迭代后已经完全预热,然后才开始吐出基准测试数据。
那么,尽管进行了热身,但表现仍然不可预测,这怎么可能呢?因为几乎直接在预热字节添加之后变得非常快,但之后它似乎再次收敛到每次添加的标称16 ns。
这些测试是在具有Intel i7 3770库存时钟和16 GB RAM的PC上运行的,因此我无法超过700000次迭代。如果这很重要,它运行在Windows 8.1 64位上。
事实证明,JIT正在优化一切,正如raphw的建议一样。
因此,我将基准测试方法替换为以下内容:
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.999226666666 ns
基准:byteArrayCheck / 迭代:400000 / 每次迭代时间:1806.854815 ns
基准:byteArrayCheck / 迭代:500000 / 每次迭代时间:1784.09091 ns
基准:字节数组检查/迭代:600000 /每次迭代时间:1804.6096366666666 ns
基准:字节数组检查/迭代:700000 /每次迭代时间:1811.0597585714286 ns
我想说的是,就计算时间而言,这些结果看起来更有说服力。但是,我的问题仍然存在。通过随机时间的重复测试,相同的模式仍然是迭代次数较少的基准测试比迭代次数较多的基准测试更快,尽管它们似乎稳定在100,000次迭代或更低的水平。
这是什么解释?