下面给出一个基准测试的例子, 创建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个线程, 但是入参是同一个实例, 竞争会非常激烈
@Benchmark
public 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 Units
Sample3_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个线程去一起执行
//每个线程的入参都是不同的
@Benchmark
public 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 Units
Sample3_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;
}
}
@Benchmark
public 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里面做一些断言操作
}
}
@Benchmark
public 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();
}
}