你的努力,终将成就无可替代的本身
未来的你必定会感谢如今拼命的本身
在平常开发中,咱们每每须要优化咱们本身写的代码。优化后的代码,执行效率是否比以前的还高?具体高多少?这些都是须要去测量。
目前比较主流的作法是使用 jmh
进行微基准测试。java
jmh
是 java
用于微基准测试工具套件。主要是基于方法层面的基准测试,精度可达纳秒级。由 oracle
实现 JIT
大牛编写而成。oracle
在使用 jmh
以前,咱们每每会先经过各类工具(jvisualvm
)找到热点代码, 而后再对热点代码使用 jmh 进行量化分析。app
下面使用字符串拼接做为案例介绍maven
第一步:加入依赖
maven
中引入 jmh jar
包ide
<dependency> <groupId>org.openjdk.jmh</groupId> <artifactId>jmh-core</artifactId> <version>1.0</version> </dependency> <dependency> <groupId>org.openjdk.jmh</groupId> <artifactId>jmh-generator-annprocess</artifactId> <version>1.0</version> <scope>provided</scope> </dependency>
第二步:编写基准测试
接下来,建立测试类,来判断 +
仍是 StringBuilder.append()
吞吐量更高函数
@BenchmarkMode(Mode.Throughput) @Warmup(iterations = 3) @Measurement(iterations = 3, time = 5, timeUnit = TimeUnit.SECONDS) @Threads(1) @Fork(1) @State(Scope.Benchmark) @OutputTimeUnit(TimeUnit.MILLISECONDS) public class StringBenchmark { String a = "1"; String b = "2"; String c = "3"; @Benchmark public String builderBenchmark() { return new StringBuilder().append(a).append(b).append(c).toString(); } @Benchmark public String connectionBenchmark() { return a + b + c; } public static void main(String[] args) throws RunnerException { Options options = new OptionsBuilder() .include(StringBenchmark.class.getSimpleName()) .build(); new Runner(options).run(); } }
第三步:查看执行结果
# Warmup: 3 iterations, 1 s each # Measurement: 3 iterations, 5 s each # Threads: 1 thread, will synchronize iterations # Benchmark mode: Throughput, ops/time # Benchmark: com.csp.boot.jmh.StringBenchmark.builderBenchmark
以上输出来自于咱们的配置。
第一行表示预热 3 次,每次 1 秒。
第二行表示运行 3 次,每次运行 5 秒。
第三行表示 1 个线程运行
第四行表示统计的数据纬度为吞吐量工具
# Run progress: 0.00% complete, ETA 00:00:36 # Fork: 1 of 1 # Warmup Iteration 1: 27694.373 ops/ms # Warmup Iteration 2: 47351.819 ops/ms # Warmup Iteration 3: 60008.968 ops/ms Iteration 1: 65411.091 ops/ms Iteration 2: 64443.826 ops/ms Iteration 3: 65067.621 ops/ms
# Fork
表示子进程。由于只配置了 1 个,因此只有一个进程执行结果。# Warmup Iteration
为预热的数据,不会被计入统计,咱们配置了 3 次预热,因此有 3 个结果。Iteration
方法执行的结果性能
Result: 64974.180 ±(99.9%) 8945.921 ops/ms [Average] Statistics: (min, avg, max) = (64443.826, 64974.180, 65411.091), stdev = 490.356 Confidence interval (99.9%): [56028.259, 73920.100]
统计结果给出了屡次测量后的最小值、均值,最大值,以及标准差(stdev),置信区间。测试
Benchmark Mode Samples Score Score error Units builderBenchmark thrpt 3 64974.180 8945.921 ops/ms connectionBenchmark thrpt 3 63524.697 69103.252 ops/ms
在最后,会给出 2 个基准测试的性能对比。
从上面结果来看,使用 +
和 StringBuilder.append()
吞吐量差很少,缘由在于,+
在编译时,会使用 StringBuilder.append()
追加字符。优化
@BenchmarkMode
BenchmarkMode
为使用模式,可选值以下:
Mode.Throughput
:吞吐量模式
AverageTime
: 表示每次执行时间
SampleTime
: 表示采样时间
SingleShotTime
: 表示只运行一次,用于测试冷启动消费时间
All
: 表示统计前面全部指标
@Warmup
配置预热次数,本例是 3
@Measurement
配置执行次数,本例是运行 5 秒,总共执行 3 次。若是是作性能测试,默认使用 1 秒便可
@Threads
配置同时执行多少个线程,默认值是Runtime.getRuntime().availableProcessors()
,本例采用 1
@Fork
启动多少个子进程分别测试每一个被@Benchmark
标识的方法,本例采用 1
@OutputTimeUnit
统计结果的时间单元,本例是TimeUnit.MILLISECONDS
@Benchmark
用于标识哪些方法须要被测试
@State
通常而言,性能测试,都会引用一些外部的对象,jmh
要求必须设置外部变量的做用域。可使用@State
表示外部对象的做用域。@State
做用于类上,被@State
标识的对象是在Thread
范围内仍是在Benchmark
。若是是Thread
,则会为每一个线程,单首创建对象。若是是Benchmark
则全部测试共享。
本例的外部变量为a b c
,@State
值为Benchmark
。
@Setup、@TearDown
2 个注解,均做用于方法上。@Setup
用于测试前的初始化工做;@TearDown
用于回收某些资源
@Param
指定某项参数的多种状况,特别适合用来测试一个函数在不一样的参数输入的状况下的性能,只能做用在字段上,使用该注解必须定义@State
注解。
为了不 JIT
优化。所以对于被测试方法,尽可能把结果返回。例如如下这段代码,会由于 i
没有被使用,而直接不执行 for
循环
public void add() { int i = 12; for (int j = 0; j < 12; j++) { i += j; } }
正常的代码以下
public int add() { int i = 12; for (int j = 0; j < 12; j++) { i += j; } return i; }