使用JMH进行简单的微基准测试

3

Stack Overflow 的另一个问题 的启发,我编写了一个微基准测试来检查哪种方式更有效:

  • 有条件地检查零的除数或
  • 捕捉和处理 ArithmeticException

以下是我的代码:

@State(Scope.Thread)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public class MyBenchmark {

    private int a = 10;
    // a little bit less obvious than int b = 0;
    private int b = (int) Math.floor(Math.random());

    @Benchmark
    public float conditional() {
        if (b == 0) {
            return 0;
        } else {
            return a / b;
        }
    }

    @Benchmark
    public float exceptional() {
        try {
            return a / b;
        } catch (ArithmeticException aex) {
            return 0;
        }
    }
}

我对JMH完全不了解,不确定代码是否正确。

我的基准测试是否正确?您看到任何错误吗?

顺便说一下:请不要建议在https://codereview.stackexchange.com上询问。对于Codereview,代码必须已经按预期工作。我不确定这个基准测试是否按预期工作。

1个回答

2
我看到缺少的一个关键点是任何形式的随机性,这可以让分支预测更轻松地完成工作,从而使两种方法比实际上除以0要快。
我会对每种方法做三个变体:
1. 带有零混合的随机数组,并将基准测试参数化为该数组中的索引。 2. 带有非零数字的随机数组 3. 所有为0
这应该能够给您一个关于整体性能的好想法,包括分支预测。对于第一点,尝试玩弄0和非0的比例可能也很有趣。
我忘记了JMH是否允许直接在数组的各个值上进行参数化。如果可以,那我会使用它。否则,您将必须对该数组的索引进行参数化。在这种情况下,我还将把所有0放入一个数组中,以便停留访问成为所有测试的一部分。我还可能创建一个“控制”来访问数组并返回其值,以便您可以更直接地找出开销。
此外,一个小问题:我认为您不需要返回浮点数,因为它们只会从实际产生的整数中进行转换。

1
关于参数化,我认为,正确的方式是使用一个 int 参数来确定百分比,相应地填充一个数组,然后在设置中对其进行洗牌。在基准测试中,我要么循环遍历数组,要么在每一步上计算 index = (index+1) & array.length-1(使用长度为2的幂的数组)。你可以跳过第2和第3点,因为它们是特殊情况。 - maaartinus
@maaartinus 这是一个潜在的好简化。不过,据我上次检查,JMH 强烈建议不要在基准方法内部进行循环。我对其中的细微差别并不确定。 - yshavit
我知道,这就是为什么我提出了另一种选择。然而,我猜测那里的开销比循环要高(尽管我希望边界检查现在已经消失了(请参见此问题底部)。 - maaartinus

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接