下面给出一个基准测试的例子, 创建50个线程去并发执行一个方法
public static void main(String[] args) throws RunnerException {Options opt = new OptionsBuilder().include(Sample3_States.class.getSimpleName()).threads(50).forks(1).build();new Runner(opt).run();}
1. 整个Benchmark共用一个入参
直接看完整代码
@Warmup(iterations = 1, time = 5, timeUnit = TimeUnit.SECONDS)@Measurement(iterations = 1, time = 5, timeUnit = TimeUnit.SECONDS)public class Sample3_States {//这个静态内部类会在整个Benchmark启动的时候初始化, 作为入参//所有测试线程共享一个实例, 用于测试有状态实例在多线程共享下的性能//一般用来测试多线程竞争下的性能@State(Scope.Benchmark)//整个测试总共用这一个对象public static class BenchmarkState {volatile double x = Math.PI;}//这个依然启动4个线程, 但是入参是同一个实例, 竞争会非常激烈@Benchmarkpublic void measureShared(BenchmarkState state) {//此次基准测试所有的线程入参都是同一个对象state.x++;}public static void main(String[] args) throws RunnerException {Options opt = new OptionsBuilder().include(Sample3_States.class.getSimpleName()).threads(50).forks(1).build();new Runner(opt).run();}}
执行结果
Benchmark Mode Cnt Score Error UnitsSample3_States.measureShared thrpt 40765443.353 ops/s
这个数字是 4000万
此Benchmark创建了50个线程一起执行这个方法, 互相会有线程竞争这个 volatile 变量导致效率降低
2. 线程专属入参
直接看完整代码
@Warmup(iterations = 1, time = 5, timeUnit = TimeUnit.SECONDS)@Measurement(iterations = 1, time = 5, timeUnit = TimeUnit.SECONDS)public class Sample3_States {//这个静态内部类会在Benchmark 各个线程执行之前 初始化, 作为入参//所有测试线程各用各的@State(Scope.Thread)//不同线程不同的对象public static class ThreadState {volatile double x = Math.PI;}//根据main方法, 会启动4个线程去一起执行//每个线程的入参都是不同的@Benchmarkpublic void measureUnshared(ThreadState state) { //每个线程的入参都不同state.x++;}public static void main(String[] args) throws RunnerException {Options opt = new OptionsBuilder().include(Sample3_States.class.getSimpleName()).threads(50).forks(1).build();new Runner(opt).run();}}
执行结果:
Benchmark Mode Cnt Score Error UnitsSample3_States.measureUnshared thrpt 1670970565.887 ops/s
这个数字是16亿
由于这个变量是线程独享的, 不存在竞争的情况, 所以执行效率会非常高, 直接提升了40倍左右的效率
@State还有一种线程组的作用域 @State(Scope._Group_), 这里就不详细介绍了, 因为使用场景实在太少, 既影响可读性, 测试结果也模棱两可完全违背基准测试的目的
3. 入参初始化@Setup
通过使用@Setup注解可以在基准测试的不同生命周期 调用指定方法
@Warmup(iterations = 1, time = 5, timeUnit = TimeUnit.SECONDS)@Measurement(iterations = 1, time = 5, timeUnit = TimeUnit.SECONDS)public class Sample3_States {@State(Scope.Benchmark)//整个测试总共用这一个对象public static class BenchmarkState {volatile double x;//@Setup(Level.Trial) //整个基准测试之前只会被调用一次//@Setup(Level.Iteration) //每轮基准测试之前都会被调用一次@Setup(Level.Invocation) //每次基准测试的方法被调用之前,都会被调用一次public void setup(){System.out.println("初始化");x = Math.PI;}}@Benchmarkpublic void measureShared(BenchmarkState state) {state.x++;}public static void main(String[] args) throws RunnerException {Options opt = new OptionsBuilder().include(Sample3_States.class.getSimpleName()).threads(50).forks(1).build();new Runner(opt).run();}}
@Setup(Level.Trial)//整个基准测试只会被调用一次@Setup(Level.Iteration)//每轮基准测试都会被调用一次@Setup(Level.Invocation)//每次基准测试的方法被调用前,都会被调用一次
4. 销毁入参@TearDown
虽然叫”销毁”, 但不一定非要做销毁的操作
@Warmup(iterations = 1, time = 5, timeUnit = TimeUnit.SECONDS)@Measurement(iterations = 1, time = 5, timeUnit = TimeUnit.SECONDS)public class Sample3_States {@State(Scope.Benchmark)//整个测试总共用这一个对象public static class BenchmarkState {volatile double x;//@TearDown(Level.Trial) //整个基准测试之后,只会被调用一次//@TearDown(Level.Iteration) //每轮基准测试之后,都会被调用一次@TearDown(Level.Invocation) //每次基准测试的方法被调用之后,都会被调用一次public void tearDown(){System.out.println("销毁操作");assert x > Math.PI : "Nothing changed?"; //一般可以在tearDown里面做一些断言操作}}@Benchmarkpublic void measureShared(BenchmarkState state) {state.x++;}public static void main(String[] args) throws RunnerException {Options opt = new OptionsBuilder().include(Sample3_States.class.getSimpleName()).threads(50).forks(1).jvmArgs("-ea") //开启断言检测;assertion在一般情况下是关闭的,通过java -ea 可以打开该功能,关闭为 -da.build();new Runner(opt).run();}}
