这里说道的基准测试Benchmark其实是微基准测试Micro-Benchmark。这里面的概念就不详细介绍了,反正就是JMH可以非常方便的帮助我们进行java代码的简单基准测试。
有什么用
可以对多组代码进行基准测试比较。
很多人总说,这样用速度快,性能好,别听他们的,自己试过才知道。
Java的基准测试需要注意的几个点
测试前需要预热。
防止无用代码进入测试方法中。
并发测试。
测试结果呈现。
简单的代码例子
我们来看一个简单的示例,大家就知道JMH的强大了。
测试代码如下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
package me.irfen.jmh; import org.openjdk.jmh.annotations.*; import java.util.concurrent.TimeUnit; /** * author: zhaoye * date: 2016/11/4 14:36 */ @BenchmarkMode(Mode.Throughput) @Warmup(iterations = 3) @Measurement(iterations = 10, time = 5, timeUnit = TimeUnit.SECONDS) @Threads(16) @OutputTimeUnit(TimeUnit.MILLISECONDS) public class StringBuilderTest { @Benchmark public void testStringAdd() { String a = ""; for (int i = 0; i < 10; i++) { a += i; } print(a); } @Benchmark public void testStringBuilderAdd() { StringBuilder sb = new StringBuilder(); for (int i = 0; i < 10; i++) { sb.append(i); } print(sb.toString()); } private void print(String a) { } } |
代码中有好多注解,我们一会再说,先说我们要干什么。这里应该很好看出来,大家都说String进行字符相加的时候没有StringBuilder快,怎么证明,则就是我们要做的事情。
现在来解释注解
@Setup
这次我们没用到,简单说,这个注解的作用就是我们需要在测试之前进行一些准备工作,比如对一些数据的初始化之类的。。
@BenchmarkMode
基准测试类型。这里选择的是Throughput也就是吞吐量。根据源码点进去,每种类型后面都有对应的解释,比较好理解,吞吐量会得到单位时间内可以进行的操作数。
下面看下这里的源码。
1 2 3 4 5 |
Throughput("thrpt", "Throughput, ops/time"), AverageTime("avgt", "Average time, time/op"), SampleTime("sample", "Sampling time"), SingleShotTime("ss", "Single shot invocation time"), All("all", "All benchmark modes"); |
@Warmup
上面我们提到了,进行基准测试前需要进行预热。一般我们前几次进行程序测试的时候都会比较慢,所以要让程序进行几轮预热,保证测试的准确性。其中的参数iterations也就非常好理解了,就是预热轮数。
@Measurement
度量,其实就是一些基本的测试参数。iterations进行测试的轮次,time每轮进行的时长,timeUnit时长单位。都是一些基本的参数,可以根据具体情况调整。一般比较重的东西可以进行大量的测试,放到服务器上运行。
@Threads
测试线程,这个非常好理解,根据具体情况选择,一般为cpu乘以2。
@OutputTimeUnit
这个比较简单了,基准测试结果的时间类型。一般选择秒或者毫秒。
@State
当使用@Setup参数的时候,必须在类上加这个参数,不然会提示无法运行。
执行方式
生成jar执行
一般对于大型的测试,需要测试时间比较久,线程比较多的话,就需要去写好了丢到linux程序里执行,不然本机执行很久时间什么都干不了了。
1 2 |
$ mvn clean install $ java -jar target/benchmarks.jar |
首先编译,然后执行就可以了。当然在执行的时候可以输入-h参数来看帮助。
在IDE中执行
对于一些我们自己的一些小测试,就可以直接在IDE中执行了,还丢到linux上去太麻烦了。怎么搞呢,看下面。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
package me.irfen.jmh; import org.openjdk.jmh.runner.Runner; import org.openjdk.jmh.runner.RunnerException; import org.openjdk.jmh.runner.options.Options; import org.openjdk.jmh.runner.options.OptionsBuilder; public class Test { public static void main(String[] args) throws RunnerException { Options options = new OptionsBuilder().include(StringBuilderTest.class.getSimpleName()) .output("E:/Benchmark.log").forks(2).build(); new Runner(options).run(); } } |
执行的方法当然还是个main方法,当然你用junit也是可以的。这里其实也比较简单,new个Options,然后传入要运行哪个测试,选择基准测试报告输出文件地址,设置fork出的线程数,然后通过Runner的run方法就可以跑起来了。
报告结果
下面我们来看下报告结果。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 |
# JMH 1.14.1 (released 45 days ago) # VM version: JDK 1.8.0_71, VM 25.71-b15 # VM invoker: C:\Program Files\Java\jdk1.8.0_71\jre\bin\java.exe # VM options: -Didea.launcher.port=7532 -Didea.launcher.bin.path=C:\Program Files (x86)\JetBrains\IntelliJ IDEA 2016.2.4\bin -Dfile.encoding=UTF-8 # Warmup: 3 iterations, 1 s each # Measurement: 10 iterations, 5 s each # Timeout: 10 min per iteration # Threads: 16 threads, will synchronize iterations # Benchmark mode: Throughput, ops/time # Benchmark: me.irfen.jmh.StringBuilderTest.testStringAdd # Run progress: 0.00% complete, ETA 00:03:32 # Fork: 1 of 2 # Warmup Iteration 1: 14690.103 ops/ms # Warmup Iteration 2: 18731.179 ops/ms # Warmup Iteration 3: 19475.241 ops/ms Iteration 1: 18787.224 ops/ms Iteration 2: 18949.244 ops/ms Iteration 3: 18715.979 ops/ms Iteration 4: 18770.003 ops/ms Iteration 5: 18912.926 ops/ms Iteration 6: 19252.784 ops/ms Iteration 7: 18975.545 ops/ms Iteration 8: 18767.721 ops/ms Iteration 9: 18976.551 ops/ms Iteration 10: 18830.076 ops/ms # Run progress: 25.00% complete, ETA 00:03:05 # Fork: 2 of 2 # Warmup Iteration 1: 15871.759 ops/ms # Warmup Iteration 2: 19330.148 ops/ms # Warmup Iteration 3: 18851.970 ops/ms Iteration 1: 19058.934 ops/ms Iteration 2: 18851.454 ops/ms Iteration 3: 18511.584 ops/ms Iteration 4: 18951.476 ops/ms Iteration 5: 18721.372 ops/ms Iteration 6: 18891.480 ops/ms Iteration 7: 18651.455 ops/ms Iteration 8: 18854.140 ops/ms Iteration 9: 19198.246 ops/ms Iteration 10: 19148.773 ops/ms Result "testStringAdd": 18888.848 ±(99.9%) 160.591 ops/ms [Average] (min, avg, max) = (18511.584, 18888.848, 19252.784), stdev = 184.936 CI (99.9%): [18728.258, 19049.439] (assumes normal distribution) # JMH 1.14.1 (released 45 days ago) # VM version: JDK 1.8.0_71, VM 25.71-b15 # VM invoker: C:\Program Files\Java\jdk1.8.0_71\jre\bin\java.exe # VM options: -Didea.launcher.port=7532 -Didea.launcher.bin.path=C:\Program Files (x86)\JetBrains\IntelliJ IDEA 2016.2.4\bin -Dfile.encoding=UTF-8 # Warmup: 3 iterations, 1 s each # Measurement: 10 iterations, 5 s each # Timeout: 10 min per iteration # Threads: 16 threads, will synchronize iterations # Benchmark mode: Throughput, ops/time # Benchmark: me.irfen.jmh.StringBuilderTest.testStringBuilderAdd # Run progress: 50.00% complete, ETA 00:02:04 # Fork: 1 of 2 # Warmup Iteration 1: 84345.885 ops/ms # Warmup Iteration 2: 118411.934 ops/ms # Warmup Iteration 3: 69339.009 ops/ms Iteration 1: 74770.077 ops/ms Iteration 2: 76996.804 ops/ms Iteration 3: 77855.877 ops/ms Iteration 4: 76883.502 ops/ms Iteration 5: 75685.537 ops/ms Iteration 6: 76299.372 ops/ms Iteration 7: 77065.766 ops/ms Iteration 8: 76537.937 ops/ms Iteration 9: 77216.754 ops/ms Iteration 10: 75410.698 ops/ms # Run progress: 75.00% complete, ETA 00:01:02 # Fork: 2 of 2 # Warmup Iteration 1: 78998.331 ops/ms # Warmup Iteration 2: 61750.531 ops/ms # Warmup Iteration 3: 69513.539 ops/ms Iteration 1: 64862.481 ops/ms Iteration 2: 65963.130 ops/ms Iteration 3: 66719.366 ops/ms Iteration 4: 66315.918 ops/ms Iteration 5: 67172.960 ops/ms Iteration 6: 66156.558 ops/ms Iteration 7: 67372.587 ops/ms Iteration 8: 67607.029 ops/ms Iteration 9: 67339.769 ops/ms Iteration 10: 65845.035 ops/ms Result "testStringBuilderAdd": 71503.858 ±(99.9%) 4491.696 ops/ms [Average] (min, avg, max) = (64862.481, 71503.858, 77855.877), stdev = 5172.644 CI (99.9%): [67012.162, 75995.554] (assumes normal distribution) # Run complete. Total time: 00:04:11 Benchmark Mode Cnt Score Error Units StringBuilderTest.testStringAdd thrpt 20 18888.848 ± 160.591 ops/ms StringBuilderTest.testStringBuilderAdd thrpt 20 71503.858 ± 4491.696 ops/ms |
内容有点多,仔细看,三大部分,第一部分是有序的结果,第二部分是无序的结果,第三部分就是两个的简单结果比较。这里注意我们forks传的2,所以每个测试有两个fork结果。
前两部分是一样的,简单说下。首先会写出每部分的一些参数设置,然后是预热迭代执行(Warmup Iteration),然后是正常的迭代执行(Iteration),最后是结果(Result)。这些看看就好,我们最关注的就是第三部分,其实也就是最终的结论。千万别看歪了,他输出的也确实很不爽,error那列其实没有内容,score的结果是xxx ± xxx,单位是没毫秒多少个操作。可以看到,StringBuilder的速度还确实是要比String进行文字叠加的效率好太多。
最后
到此我们介绍了JMH的一些简单用法。对于别人说的东西,现在jvm优化了很多,哪些还是真的,哪些是已经过时的?只有自己试过了才知道。
官网在这,http://openjdk.java.net/projects/code-tools/jmh/
其他还有很多有趣的例子,http://hg.openjdk.java.net/code-tools/jmh/file/tip/jmh-samples/src/main/java/org/openjdk/jmh/samples/
©原创文章,转载请注明来源: 赵伊凡's Blog
©本文链接地址: Java使用JMH进行简单的基准测试Benchmark
like
内容准确,简洁明了
您上面说的 打成jar测试,是怎么个测试方法,能否提供一个demo?