使用 JMH Java 微基准测试浮点打印的随机数据

Jas*_*onN 2 java performance-testing microbenchmark floating-point-conversion jmh

我正在为我编写的浮点打印代码编写 JMH 微基准测试。我还不太关心确切的性能,但要确保基准代码正确。

\n\n

我想循环一些随机生成的数据,因此我制作了一些静态数据数组并保持循环机制(增量和掩码)尽可能简单。这是正确的方法还是我应该告诉 JMH 更多关于我缺少的一些注释的情况?

\n\n

另外,是否可以为测试制作显示组而不仅仅是字典顺序?我基本上有两组测试(每组随机数据一组。

\n\n

完整来源位于https://github.com/jnordwick/zerog-grisu

\n\n

这是基准代码:

\n\n
package zerog.util.grisu;\n\nimport java.util.Random;\n\nimport org.openjdk.jmh.annotations.Benchmark;\nimport org.openjdk.jmh.runner.Runner;\nimport org.openjdk.jmh.runner.RunnerException;\nimport org.openjdk.jmh.runner.options.Options;\nimport org.openjdk.jmh.runner.options.OptionsBuilder;\n\n/* \n * Current JMH bench, similar on small numbers (no fast path code yet)\n * and 40% faster on completely random numbers.\n * \n * Benchmark                         Mode  Cnt         Score         Error  Units\n * JmhBenchmark.test_lowp_doubleto  thrpt   20  11439027.798 \xc2\xb1 2677191.952  ops/s\n * JmhBenchmark.test_lowp_grisubuf  thrpt   20  11540289.271 \xc2\xb1  237842.768  ops/s\n * JmhBenchmark.test_lowp_grisustr  thrpt   20   5038077.637 \xc2\xb1  754272.267  ops/s\n * \n * JmhBenchmark.test_rand_doubleto  thrpt   20   1841031.602 \xc2\xb1  219147.330  ops/s\n * JmhBenchmark.test_rand_grisubuf  thrpt   20   2609354.822 \xc2\xb1   57551.153  ops/s\n * JmhBenchmark.test_rand_grisustr  thrpt   20   2078684.828 \xc2\xb1  298474.218  ops/s\n * \n * This doens\'t account for any garbage costs either since the benchmarks\n * aren\'t generating enough to trigger GC, and Java internally uses per-thread\n * objects to avoid some allocations.\n * \n * Don\'t call Grisu.doubleToString() except for testing. I think the extra\n * allocations and copying are killing it. I\'ll fix that.\n */\n\npublic class JmhBenchmark {\n\n    static final int nmask = 1024*1024 - 1;\n    static final double[] random_values = new double[nmask + 1];\n    static final double[] lowp_values = new double[nmask + 1];\n\n    static final byte[] buffer = new byte[30];\n    static final byte[] bresults = new byte[30];\n\n    static int i = 0;\n    static final Grisu g = Grisu.fmt;\n\n    static {\n\n        Random r = new Random();\n        int[] pows = new int[] { 1, 10, 100, 1000, 10000, 100000, 1000000 };\n\n        for( int i = 0; i < random_values.length; ++i ) {\n            random_values[i] = r.nextDouble();\n        }\n\n        for(int i = 0; i < lowp_values.length; ++i ) {\n            lowp_values[i] = (1 + r.nextInt( 10000 )) / pows[r.nextInt( pows.length )];\n        }\n    }\n\n    @Benchmark\n    public String test_rand_doubleto() {\n        String s = Double.toString( random_values[i] );\n        i = (i + 1) & nmask;\n        return s;\n    }\n\n    @Benchmark\n    public String test_lowp_doubleto() {\n        String s = Double.toString( lowp_values[i] );\n        i = (i + 1) & nmask;\n        return s;\n    }\n\n    @Benchmark\n    public String test_rand_grisustr() {\n        String s =  g.doubleToString( random_values[i] );\n        i = (i + 1) & nmask;\n        return s;\n    }\n\n    @Benchmark\n    public String test_lowp_grisustr() {\n        String s =  g.doubleToString( lowp_values[i] );\n        i = (i + 1) & nmask;\n        return s;\n    }\n\n    @Benchmark\n    public byte[] test_rand_grisubuf() {\n        g.doubleToBytes( bresults, 0, random_values[i] );\n        i = (i + 1) & nmask;\n        return bresults;\n    }\n\n    @Benchmark\n    public byte[] test_lowp_grisubuf() {\n        g.doubleToBytes( bresults, 0, lowp_values[i] );\n        i = (i + 1) & nmask;\n        return bresults;\n    }\n\n    public static void main(String[] args) throws RunnerException {\n        Options opt = new OptionsBuilder()\n                .include(".*" + JmhBenchmark.class.getSimpleName() + ".*")\n                .warmupIterations(20)\n                .measurementIterations(20)\n                .forks(1)\n                .build();\n\n        new Runner(opt).run();\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

Ale*_*lev 6

您只能通过分析其结果来证明基准测试是正确的。基准测试代码只会引发您必须跟进的危险信号。我在您的代码中看到这些危险信号:

  1. 依赖static final字段来存储状态。这些字段的内容通常可以“内联”到计算中,从而使基准测试的某些部分变得无效。JMH 只是让您免于不断折叠对象中的常规字段@State

  2. 使用static初始化器。虽然这在当前的 JMH 中没有影响,但预期的方法是使用@Setup方法来初始化状态。对于您的情况,它还有助于获得真正随机的数据点,例如,如果您设置@Setup(Level.Iteration)在开始下一次测试迭代之前重新初始化值。

就一般方法而言,这是实现安全循环的方法之一:将循环计数器放在方法之外。还有另一种可以说是安全的方法:在方法中循环数组,但将每个迭代结果放入Blackhole.consume.