java - JMH:对环境的奇怪依赖
问题描述
在我第一次使用方法来JMH
对我的班级进行基准测试时,我遇到了一个让我感到困惑的行为,我想在继续之前澄清这个问题。
让我感到困惑的情况:
当我在 CPU 被无关进程加载(78%-80%)的情况下运行基准测试时,显示的结果JMH
看起来相当合理且稳定:
Benchmark Mode Cnt Score Error Units
ArrayOperations.a_bigDecimalAddition avgt 5 264,703 ± 2,800 ns/op
ArrayOperations.b_quadrupleAddition avgt 5 44,290 ± 0,769 ns/op
ArrayOperations.c_bigDecimalSubtraction avgt 5 286,266 ± 2,454 ns/op
ArrayOperations.d_quadrupleSubtraction avgt 5 46,966 ± 0,629 ns/op
ArrayOperations.e_bigDecimalMultiplcation avgt 5 546,535 ± 4,988 ns/op
ArrayOperations.f_quadrupleMultiplcation avgt 5 85,056 ± 1,820 ns/op
ArrayOperations.g_bigDecimalDivision avgt 5 612,814 ± 5,943 ns/op
ArrayOperations.h_quadrupleDivision avgt 5 631,127 ± 4,172 ns/op
比较大的误差是因为我现在只需要一个粗略的估计,我故意用精确来换取速度。
但在处理器上没有额外负载的情况下获得的结果对我来说似乎很惊人:
Benchmark Mode Cnt Score Error Units
ArrayOperations.a_bigDecimalAddition avgt 5 684,035 ± 370,722 ns/op
ArrayOperations.b_quadrupleAddition avgt 5 83,743 ± 25,762 ns/op
ArrayOperations.c_bigDecimalSubtraction avgt 5 531,430 ± 184,980 ns/op
ArrayOperations.d_quadrupleSubtraction avgt 5 85,937 ± 103,351 ns/op
ArrayOperations.e_bigDecimalMultiplcation avgt 5 641,953 ± 288,545 ns/op
ArrayOperations.f_quadrupleMultiplcation avgt 5 102,692 ± 31,625 ns/op
ArrayOperations.g_bigDecimalDivision avgt 5 733,727 ± 161,827 ns/op
ArrayOperations.h_quadrupleDivision avgt 5 820,388 ± 546,990 ns/op
一切似乎都慢了近两倍,迭代时间非常不稳定(在相邻迭代中可能从 500 到 1300 ns/op 不等),并且误差分别大到无法接受。
第一组结果是通过运行一堆应用程序获得的,包括FahCore_a7.exe
占用 75% CPU 时间的 Folding@home 分布式计算客户端()、一个主动使用磁盘的 BitTorrent 客户端、浏览器中的十几个选项卡、电子邮件客户端等。平均 CPU 负载约为 85%。在基准测试执行期间FahCore
减少负载,以便Java
占 25%,总负载为 100%。
第二组结果是在所有不必要的进程都停止时获取的,CPU 实际上是空闲Java
的,只占用了 25% 和几个百分比用于系统需求。
我的 CPU 是 Intel i5-4460,4 个内核,3.2 GHz,RAM 32 GB,操作系统 Windows Server 2008 R2。
java 版本 "1.8.0_231"
Java(TM) SE Runtime Environment (build 1.8.0_231-b11)
Java HotSpot(TM) 64-Bit Server VM (build 25.231-b11, 混合模式)
问题是:
- 当它是加载机器的唯一任务时,为什么基准测试显示更糟糕和不稳定的结果?
- 当第一组结果如此依赖环境时,我是否可以认为它们或多或少可靠?
- 我应该以某种方式设置环境以消除这种依赖关系吗?
- 还是这是我的代码应该受到责备?
编码:
package com.mvohm.quadruple.benchmarks;
// Required imports here
import com.mvohm.quadruple.Quadruple; // The class under tests
@State(value = Scope.Benchmark)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(java.util.concurrent.TimeUnit.NANOSECONDS)
@Fork(value = 1)
@Warmup(iterations = 3, time = 7)
@Measurement(iterations = 5, time = 10)
public class ArrayOperations {
// To do BigDecimal arithmetic with the precision close to this of Quadruple
private static final MathContext MC_38 = new MathContext(38, RoundingMode.HALF_EVEN);
private static final int DATA_SIZE = 0x1_0000; // 65536
private static final int INDEX_MASK = DATA_SIZE - 1; // 0xFFFF
private static final double RAND_SCALE = 1e39; // To provide a sensible range of operands,
// so that the actual calculations don't get bypassed
private final BigDecimal[] // Data to apply operations to
bdOp1 = new BigDecimal[DATA_SIZE], // BigDecimals
bdOp2 = new BigDecimal[DATA_SIZE],
bdResult = new BigDecimal[DATA_SIZE];
private final Quadruple[]
qOp1 = new Quadruple[DATA_SIZE], // Quadruples
qOp2 = new Quadruple[DATA_SIZE],
qResult = new Quadruple[DATA_SIZE];
private int index = 0;
@Setup
public void initData() {
final Random rand = new Random(12345); // for reproducibility
for (int i = 0; i < DATA_SIZE; i++) {
bdOp1[i] = randomBigDecimal(rand);
bdOp2[i] = randomBigDecimal(rand);
qOp1[i] = randomQuadruple(rand);
qOp2[i] = randomQuadruple(rand);
}
}
private static Quadruple randomQuadruple(Random rand) {
return Quadruple.nextNormalRandom(rand).multiply(RAND_SCALE); // ranged 0 .. 9.99e38
}
private static BigDecimal randomBigDecimal(Random rand) {
return Quadruple.nextNormalRandom(rand).multiply(RAND_SCALE).bigDecimalValue();
}
@Benchmark
public void a_bigDecimalAddition() {
bdResult[index] = bdOp1[index].add(bdOp2[index], MC_38);
index = ++index & INDEX_MASK;
}
@Benchmark
public void b_quadrupleAddition() {
// semantically the same as above
qResult[index] = Quadruple.add(qOp1[index], qOp2[index]);
index = ++index & INDEX_MASK;
}
// Other methods are similar
public static void main(String... args) throws IOException, RunnerException {
final Options opt = new OptionsBuilder()
.include(ArrayOperations.class.getSimpleName())
.forks(1)
.build();
new Runner(opt).run();
}
}
解决方案
原因很简单,我应该马上就明白了。在操作系统中启用了省电模式,这降低了 CPU 在低负载下的时钟频率。道德是,基准测试时始终禁用节能!
推荐阅读
- python - 如何检查数字中的连续数字是偶数还是奇数?
- javascript - 使用 moment.js 确定当前时间(以小时为单位)是否在特定时间之间
- angular - 带有选择的 Angular i18n img alt 属性
- laravel - 将数据从刀片模板传递到 vue 时出现 Vue 警告
- javascript - 使用 Express (Node.js) 在 React 应用程序中找不到 index.js
- javascript - 使用 day 对象检查数组
- charts - 添加新节点后无法使 Grahviz 的边缘笔直并正确渲染
- netsuite - Netsuite 在客户记录中多次插入地址
- java - 如何在不使用模型的情况下将 JCheckBox 列添加到我的 JTable?
- python - 模型提供了很好的训练和测试准确性,但在进行预测时效果不佳