大家好,又见面了,我是你们的朋友全栈君。
一、基准测试
-
基准测试是什么
-
基准测试是指通过设计科学的测试方法、测试工具和测试系统,实现对一类测试对象的某项性能指标进行定量的和可对比的测试。
-
例如,对计算机CPU进行浮点运算、数据访问的带宽和延迟等指标的基准测试,可以使用户清楚地了解每一款CPU的运算性能及作业吞吐能力是否满足应用程序的要求
-
再如对数据库管理系统的ACID(Atomicity, Consistency, Isolation, Durability, 原子性、一致性、独立性和持久性)、查询时间和联机事务处理能力等方面的性能指标进行基准测试,也有助于使用者挑选最符合自己需求的数据库系统
-
-
通过基准测试,我们可以了解某个软件在给定环境下的性能表现,对使用者而言可以用作选型的参考,对开发者而言可以作为后续改进的基本参照。
二、JMH
-
JMH是什么
三、入门
-
因为 JMH 是 JDK9 之后才自带的,如果是 JDK9 之前的版本需要加入如下依赖
<dependency> <groupId>org.openjdk.jmh</groupId> <artifactId>jmh-core</artifactId> <version>1.32</version> </dependency> <dependency> <groupId>org.openjdk.jmh</groupId> <artifactId>jmh-generator-annprocess</artifactId> <version>1.32</version> </dependency>
3.1 第一个例子:Hello JMH
import org.openjdk.jmh.annotations.Benchmark; 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 Example_01_HelloJMH { @Benchmark public String sayHello() { return "HELLO JMH!"; } public static void main(String[] args) throws RunnerException { Options options = new OptionsBuilder() .include(Example_01_HelloJMH.class.getSimpleName()) .forks(1) .build(); new Runner(options).run(); } }
3.2 运行结果
# JMH version: 1.32 # VM version: JDK 1.8.0_241, Java HotSpot(TM) 64-Bit Server VM, 25.241-b07 # VM invoker: G:\Java\jdk1.8.0_241\jre\bin\java.exe # VM options: -javaagent:G:\JetBrains\IntelliJ IDEA 2020.2.3\lib\idea_rt.jar=56180:G:\JetBrains\IntelliJ IDEA 2020.2.3\bin -Dfile.encoding=UTF-8 # Blackhole mode: full + dont-inline hint # 预热配置 # Warmup: 5 iterations, 10 s each # 检测配置 # Measurement: 5 iterations, 10 s each # 超时配置 # Timeout: 10 min per iteration # 测试线程配置 # Threads: 1 thread, will synchronize iterations # 基准测试运行模式 # Benchmark mode: Throughput, ops/time # 当前测试的方法 # Benchmark: com.ziroom.test.Example_01_HelloJMH.sayHello # 运行过程的输出 # Run progress: 0.00% complete, ETA 00:01:40 # Fork: 1 of 1 # Warmup Iteration 1: 2924740803.993 ops/s # Warmup Iteration 2: 2916472711.387 ops/s # Warmup Iteration 3: 3024204715.897 ops/s # Warmup Iteration 4: 3051723946.668 ops/s # Warmup Iteration 5: 2924014544.301 ops/s Iteration 1: 2909665054.710 ops/s Iteration 2: 2989675862.826 ops/s Iteration 3: 2965046292.629 ops/s Iteration 4: 3020263765.220 ops/s Iteration 5: 2929485177.735 ops/s # 当前方法测试结束的报告 Result "com.ziroom.test.Example_01_HelloJMH.sayHello": 2962827230.624 ±(99.9%) 171803440.922 ops/s [Average] (min, avg, max) = (2909665054.710, 2962827230.624, 3020263765.220), stdev = 44616808.022 CI (99.9%): [2791023789.702, 3134630671.547] (assumes normal distribution) # Run complete. Total time: 00:01:41 REMEMBER: The numbers below are just data. To gain reusable insights, you need to follow up on why the numbers are the way they are. Use profilers (see -prof, -lprof), design factorial experiments, perform baseline and negative tests that provide experimental control, make sure the benchmarking environment is safe on JVM/OS/HW level, ask for reviews from the domain experts. Do not assume the numbers tell you what you want them to tell. # 所有benchmark跑完后的最终报告 Benchmark Mode Cnt Score Error Units Example_01_HelloJMH.sayHello thrpt 5 2962827230.624 ± 171803440.922 ops/s
3.3 使用 JMH 测试 Spring/SpringBoot
import com.spadesk.springbootjmhtest.controller.TestController; import org.openjdk.jmh.annotations.*; 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; import org.springframework.boot.SpringApplication; import org.springframework.context.ConfigurableApplicationContext; import java.util.concurrent.TimeUnit; @BenchmarkMode(Mode.AverageTime) @OutputTimeUnit(TimeUnit.MILLISECONDS) @State(Scope.Benchmark) public class SpringBootBenchMark { public static void main(String[] args) throws RunnerException { Options options = new OptionsBuilder() .include(SpringBootBenchMark.class.getSimpleName()) .warmupIterations(3) .measurementIterations(3) .forks(3) .build(); new Runner(options).run(); } private ConfigurableApplicationContext springContext; private TestController testController; @Setup public void setUp() { // SpringbootJmhTestApplication.class是项目里的spring boot启动类 springContext = SpringApplication.run(SpringbootJmhTestApplication.class); // 加载 Bean,此时会自动注入 AService 和 BService testController = springContext.getBean(TestController.class); } @TearDown public void tearDown() { springContext.close(); } @Benchmark public void testStringBuffer() { // controller 调用 AService 的方法 testController.testAService(); } @Benchmark public void testStringBuilder() { // controller 调用 BService 的方法 testController.testBService(); } }
四、详细说明
4.1 JMH启动
-
StringBuilderRunner
-
这个类的作用就是启动基准测试。
-
-
方式一:通过命令使用Maven命令执行
-
这种适合对于大型基准测试,像那些要运行很多次,并且运行的时间也比较长的情况下
-
我们可以直接打个jar包,发到服务器上,敲个命令就不用管它,过几十分钟、几个小时甚至几天的时间再来看结果
-
-
方式二:通过Main方法运行
-
在Main方法中,通过
org.openjdk.jmh.runner.Runner
类去运行org.openjdk.jmh.runner.Runner
即可。 -
官方提供了一个
OptionsBuilder
对象去构建。这个Builder对象是流式的。 -
下面会详细介绍它的常用方法以及对应的注解
-
4.2 选项及注解
include
-
作用
-
指定要运行的基准测试类
-
-
参数
-
要运行基础测试类的简单名称,也就是
@benchmark
所在的类的名字,这里可以使用正则表达式对所有类进行匹配
-
exclude
-
和
include
相反的 -
作用
-
指定不要运行的基准测试类
-
-
参数
-
不要运行基准测试类的简单名称
-
timeUnit
-
作用
-
全局设置时间的单位。
-
-
参数
类型 | 描述 | 备注 |
---|---|---|
NANOSECONDS | 纳秒 | – |
MICROSECONDS | 微秒 | 1微秒=1000纳秒 |
MILLISECONDS | 毫秒 | 1毫秒=100微秒 |
SECONDS | 秒 | 1秒=1000毫秒 |
MINUTES | 分钟 | 1分钟=60秒 |
HOURS | 小时 | 1小时=60分钟 |
DAYS | 天 | 1天=24小时 |
@Benchmark
-
方法注解
-
作用
-
指定需要进行测试的方法。
-
-
Benchmark
对基准有效负载进行了划分,JMH专门将其视为包含基准代码的包装器。为了可靠地运行基准测试,JMH为这些包装器方法强制执行一些严格的属性,包括但不限于:-
被注解的方法必须是
public
-
参数只能包括
State
注解的类(JMH将在调用该方法时进行注入)或JMH基础设施类(org.openjdk.jmh.infra.Control
,org.openjdk.jmh.infra.Blackhole
) -
方法只能在将相关
State
放置在封闭类上时进行同步。
-
-
如果要对破坏这些属性的方法进行基准测试,您必须把它们写成不同的方法,并从
Benchmark
方法中调用它们。 -
基准测试方法可以声明要抛出的异常和可丢弃项。任何实际引发和抛出的异常都将被视为基准测试失败。
@BenchmarkMode- mode
-
类或方法注解
-
作用
-
指定基准测试的模式
-
-
mode的参数是
Mode
类型,默认Mode.Throughput
-
@BenchmarkMode的参数是
Mode
类型数组,默认Mode.Throughput
-
Mode
枚举类枚举了JMH的测试模式,分别为
模式 | 介绍 | 单位 |
---|---|---|
Throughout | 整体吞吐量,例如“1秒内可以执行多少次调用” | op/time |
AverageTime | 平均时间,执行程序的平均耗时,例如“每次调用平均耗时xxx毫秒” | time/op |
SampleTime | 执行时间随机取样,输出执行时间的结果分布,例如“99%的调用在xxx毫秒以内,99.99%的调用在xxx毫秒以内” | time/op |
SingleShotTime | 以上模式都是默认一次 iteration 是 1s,唯有 SingleShotTime 是只运行一次。往往同时把 warmup 次数设为0,用于测试冷启动时的性能 | time/op |
All | 全部模式 | – |
单位中的
op
表明的是一次操作,默认一次操作指的是执行一次测试方法。可是咱们能够指定调用多少次测试方法算做一次操作。在 JMH 中称做操作中的批处理次数,例如咱们能够设置执行五次测试方法算做一次操作。
预热
-
因为 JVM 会使用 JIT 即时编译器对热点代码进行编译,所以同一份代码可能因为执行次数的增长而致使执行时间差别太大,所以咱们可让代码先预热几轮,预热时间不算入测量计时。
@WarmUp
-
类或方法注解
-
作用
-
用于指定预热的次数、批处理数量、时间和时间单位。
-
-
参数均可以在
Options
中单独指定,优先级是:类 < 方法 <Options
。
参数 | 描述 | Options 中方法 |
备注 |
---|---|---|---|
iterations | 测量次数,默认是5次 | warmupIterations(int value) | |
batchSize | 每次操作的批处理次数,默认是1,即执行一次操作调用一次预热方法 | warmupBatchSize(int value) | |
time | 单次预热持续时间,默认是10 | warmupTime(TimeValue value) | |
timeUnit | 时间单位,指定time 单位,默认是秒 |
– |
相关方法
-
warmupMode
-
作用 – 指定预热的模式
-
参数类型
WarmupMode
-
WarmupMode
枚举类枚举了预热模式,分别为
参数 描述 备注 INDI 对每个基准进行单独的热身 BULK 在任何基准测试开始之前进行批量预热。 BULK_INDI 在任何基准测试开始之前进行批量预热,然后对每个基准测试进行单独预热 -
-
warmupForks
-
作用 – 预热的进程数
-
-
includeWarmup
-
作用 – 在这一过程中,还有哪些其他的热身基准
-
避免 JIT 优化
-
使用
@WarmUp
对代码进行预热,防止前后执行的时间差别。但这仅仅只是解决时间前后存在的问题,JIT 还存在着其余很是多的优化手段,这是 JVM 牛逼的地方,但倒是 JMH 难受的地方。 -
JIT 优化技术列表:PerformanceTacticIndex – PerformanceTacticIndex – OpenJDK Wiki (java.net)
测量
@Measurement
-
类或方法注解
-
作用
-
用于指定测试的次数、批处理数量、时间和时间单位。
-
-
参数均可以在
Options
中单独指定,优先级是:类 < 方法 <Options
。
参数 | 描述 | Options 中的方法 |
备注 |
---|---|---|---|
iterations | 测量次数,默认是5次 | measurementIterations(int var1) | |
batchSize | 每次操作的批处理次数,默认是1,即执行一次操作调用一次测试方法 | measurementBatchSize(int var1) | |
time | 单次测量持续时间,默认是10 | measurementTime(TimeValue var1) | |
timeUnit | 时间单位,指定time 单位,默认是秒 |
– |
@Forks – forks
-
类或方法注解
-
作用
-
设置基准测试的默认fork参数。
-
-
参数
参数 | 描述 | 默认值 | 备注 |
---|---|---|---|
int value() | 测试时fork的数,0表示不进行fork | BLANK_FORKS | int BLANK_FORKS = -1; |
int warmups() | 预热时的fork数 | BLANK_FORKS | String BLANK_ARGS = “blank_blank_blank_2014”; |
String jvm() | 要运行的JVM | BLANK_ARGS | Options中的jvm 作用一样 |
String[] jvmArgs() | 要在命令行中替换的JVM参数 | { BLANK_ARGS } | Options中的jvmArgs 作用一样,参数为String 可变长参数 |
String[] jvmArgsPrepend() | 要在命令行中添加的JVM参数 | { BLANK_ARGS } | Options中的jvmArgsPrepend 作用一样,参数为String 可变长参数 |
String[] jvmArgsAppend() | 要在命令行中追加的JVM参数 | { BLANK_ARGS } | Options中的jvmArgsAppend 作用一样,参数为String 可变长参数 |
-
说明
JVM因为使用了profile-guided optimization而“臭名昭著”,这对于微基准测试来说十分不友好,因为不同测试方法的profile混杂在一起,“互相伤害”彼此的测试结果。对于每个
Benchmark
方法使用一个独立的进程可以解决这个问题,这也是JMH的默认选项。注意不要设置为0,设置为n则会启动n个进程执行测试(似乎也没有太大意义)。 fork选项也可以通过方法注解以及启动参数来设置
detectJvmArgs
-
作用
-
自动检测来自父VM的fork JVM参数。重写jvmArgs()的值
-
@Threads – threads
-
类或方法注解
-
作用
-
要运行的线程数。一般选择为cpu乘以2。如果配置了 Threads.MAX ,代表使用Runtime.getRuntime().availableProcessors() 个线程。
-
-
参数
-
需要的线程数量,类型
int
-
@Timeout – timeout
-
类或方法注解
-
作用
-
设置超时时间
-
-
参数
-
超时的时间,类型
int
,默认10 -
时间单位,类型
TimeUnit
,默认SECONDS
(秒)
-
@OutputTimeUnit
-
类或方法注解
-
作用
-
提供报告结果的默认时间单位
-
-
参数
-
结果显示的时间单位,类型
TimeUnit
-
@State
-
类注解
-
作用
-
通过 State 可以指定一个对象的作用范围,JMH根据 scope 来进行实例化和共享操作。@State 可以被继承使用,如果父类定义了该注解,子类则无需定义。可以类比Spring Bean的Scope。
-
-
参数
-
State的范围,类型
Scope
-
Scope
包含如下:
类型 描述 备注 Thread 默认的State,每个测试线程分配一个实例; Benchmark 所有测试线程共享一个实例,用于测试有状态实例在多线程共享下的性能; Group 每个线程组共享一个实例; -
@Setup
-
方法注解
-
作用
-
在执行 benchmark方法之前被执行,正如其名,主要用于初始化,比如对一些数据的初始化之类的。
-
-
参数
-
被注解方法的级别,类型
Level
-
Level
参数表明粒度,粒度从粗到细分别是
类型 描述 备注 Trial 默认级别。每个Benchmark方法前后 Iteration 每个Benchmark方法每次迭代前后 Invocation 每个Benchmark方法每次调用前后,谨慎使用; -
-
特性
-
由于fixture方法管理
State
生命周期,Setup
只能在State
类中使用。 -
Setup
方法将由一个有权访问State
的线程执行,并且没有确切定义哪个线程。 -
如果
State
在线程之间共享,那意味着TearDown
可能由不同的线程执行。
-
@TearDown
-
方法注解,前置注解
State
-
作用
-
@Setup 相对的,会在所有 benchmark 执行结束以后执行,主要用于资源的回收等。
-
-
参数
-
被注解方法的级别,类型
Level
,和@Setup
一样
-
-
特性
-
由于fixture方法管理
State
生命周期,TearDown
只能在State
类中使用。 -
TearDown
方法将由一个有权访问State
的线程执行,并且没有确切定义哪个线程。 -
如果
State
在线程之间共享,那意味着Setup
可能由不同的线程执行。
-
@Param – param
-
属性注解,使用该注解必须定义
@State
注解 -
作用
-
可以用来指定某项参数的多种情况。特别适合用来测试一个函数在不同的参数输入的情况下的性能。
-
-
参数
-
参数值序列,类型
String
数组,默认值{ BLANK_ARGS }
,String BLANK_ARGS = "blank_blank_blank_2014";
-
默认情况下,将在给定顺序的运行期间遍历参数值。
-
param有两个参数,一个是注入参数的参数名;另一个是注入参数的值序列,类型为
String
可变长参数
-
-
特性
-
注解字段不能是
final
字段,只能定义在State
类中. -
在调用任何
Setup
方法之前,JMH将把值注入带注释的字段中。 -
不能保证字段值在任何初始值设定项或
State
的任何构造方法中都可以访问。 -
参数可接收任何基础类型、基础类型包装类、字符串或枚举。注解值以字符串形式给出,并将根据需要进行强制转化来匹配字段类型。
-
当基准测试运行需要多个参数时,JMH将计算运行中所有参数的外积(个人理解应该是运行内存)。
-
@Group
-
方法注解
-
作用
-
执行组(线程组)
-
给
Benchmark
方法分组
-
-
参数
-
组标记,有效的Java标识符。,类型
String
,默认值为group。
-
-
特性
-
可以在执行组中绑定多个
Benchmark
方法以生成非对称基准测试。 -
每个执行组包含一个或多个线程。特定执行组中的每个线程执行一个带
Group
注解的Benchmark
方法。 -
执行特定
Benchmark
方法的线程数默认为单个线程,可以在GroupThreads
设置线程数。 -
一个执行组可能有多个副本参与运行,而参与运行的执行组的数量取决于请求的工作线程的数量。
-
JMH将请求到的工作线程数四舍五入到执行组大小,然后在多个组之间分配线程。
-
例如,运行
Group
中有两个Benchmark
方法,每个方法都有GroupThreads(4)
,将运行8*N个线程,其中N是一个整数。 -
组标记用作生成的基准名称。隔离中每个基准方法的结果都被记录为由原始方法名称命名的辅助结果。
-
@GroupThreads
-
方法注解
-
作用
-
定义有多少个线程参与执行组中特定
Benchmark
方法的运行。 -
给执行组分配线程数
-
-
参数
-
分配的线程数,类型
int
,默认1个线程
-
@OperationsPerInvocation – operationsPerInvocation
-
类或方法注解
-
作用
-
该注解允许传达基准测试不止一次操作,并允许JMH适当调整分数。
-
每调用一次方法算多少次操作(一次方法调用 = n 次操作,n 可配置)
-
例如,使用内部循环进行多个操作的基准测试可能需要测量单个操作的性能:
@Benchmark @OperationsPerInvocation(10) public void test() { for (int i = 0; i <= 10; i++) { // do something } } // 调用一次 test() 方法,相当于10次操作。
-
-
参数
-
每个
Benchmark
方法调用的操作数,类型int
,默认为1
-
-
代码说明
@Benchmark public void test1() { for (int i = 0; i <= 10; i++) { // do something } } @Benchmark @OperationsPerInvocation(10) public void test2() { for (int i = 0; i <= 10; i++) { // do something } }
-
结果
test1:test1·p0.99 sample ≈ 10⁻⁶ s/op test1:test1·p0.999 sample ≈ 10⁻⁶ s/op test2:test2·p0.99 sample ≈ 10⁻⁷ s/op test2:test2·p0.999 sample ≈ 10⁻⁷ s/op
-
-
补充
-
JMH结果的统计是以操作为单位,如每一个操作的响应时间,单位时间内通过的操作数(吞吐量),一个方法视为一个操作(默认情况
@OperationsPerInvocation(1)
)。你可以手动调整一个方法代表几个操作,如果把操作数设置为10,相同的方法,在输出结果中,吞吐量会提高10倍左右,响应时间快10倍左右。
-
@ComlilerControl
-
类或方法注解
-
作用
-
可用于影响基准中特定方法的编译
-
JMH通过CompilerCommand接口方法影响JVM
-
该注解只有在多个fork时有效;单个fork将无法将参数传递给编译器。
-
另外,该注解可能会被编译器忽略,然后不操作或其他无效的方法。可以检查编译器日志或输入代码查看是否有结果
-
-
参数
-
编译模式,类型内部枚举
Mode
-
Mode
枚举分别为
类型 描述 备注 BREAK 将断点插入生成的编译代码。 PRINT 打印该方法及其配置文件。 EXCLUDE 从编译中排除该方法。 INLINE 强制内联。 DONT_INLINE 强制跳过内联。 COMPILE_ONLY 仅编译此方法,而不编译其他方法。 -
@AuxCounters
-
类注解
-
作用
-
该注解可用于将
State
对象标记为辅助次要结果的承载器。使用此注释标记类将使JMH将其公共字段和返回结果的公共方法作为次要基准度量的基础。
-
-
参数
-
统计类型,类型内部枚举
Type
-
Type
枚举分别为
类型 描述 备注 OPERATIONS 统计“操作”,这与执行 Benchmark
方法的次数相关。如果该计数器在每次Benchmark
方法调用时递增,那么它将生成一个类似于主基准结果的度量。该计数器将由线束标准化为基准时间。EVENTS 统计“事件”,即工作负载生命周期中的一次性事件。此计数器将无法标准化为时间。 -
-
特性
-
辅助计数器不适用于每个基准模式(
BenchmarkMode
),因为并非每个模式都计算时间或操作。Mode.AverageTime
和Mode.Throughput
,只支持吞吐量。 -
该注释仅适用于
Scope.Thread
状态对象。将其与其他状态一起使用时会编译错误。这意味着计数器本质上是本地的线程。 -
只有公共字段和方法被视为度量。如果不想将字段或方法捕获为度量,请不要将其设为
public
。 -
只有数值字段和数值返回方法会被视为度量。这些包括所有基本类型及其对应的包装类型,除了
boolean/Boolean
和char/Character
。使用类型不兼容的公共字段/方法会编译错误。 -
具有
void
返回类型的方法不必进行类型检查。这意味着Setup
和TearDown
方法和AuxCounters
一起使用比较好。 -
AuxCounters
实例中的公共字段将在开始迭代之前重置,并在迭代结束时读取。这允许基准代码避免对这些对象进行复杂的生命周期处理。 -
计数器名称由字段或方法名称生成。计数器的命名空间在参与运行的所有状态中共享。如果哪个计数器来自哪个
AuxCounters
类存在歧义,JMH将无法编译基准测试。
-
-
注意
-
该注解是一个实验性的API,将来可能会在没有事先警告的情况下更改或删除它,谨慎使用
-
threadGroups
-
作用
-
子线程分布
-
-
参数
-
线程分布,类型
int
的可变长参数
-
syncIterations
-
作用
-
是否同步测量探测器
-
-
参数
-
类型
boolean
,默认为true
-
shouldFailOnError
-
作用
-
在第一个基准错误时是否停止运行
-
-
参数
-
类型
boolean
,默认为false
-
verbosity
-
作用
-
控制冗长级别
-
-
参数
-
级别类型,类型
org.openjdk.jmh.runner.options.VerboseMode
,该类型分别为
类型 描述 备注 SILENT 保持完全沉默(不输出) NORMAL 正常输出 默认值 EXTRA 输出额外信息 -
addProfiler
-
作用
-
在运行中添加探查器
-
-
参数
-
addProfiler(Class<? extends Profiler> profiler);
-
profiler:Profiler类
-
-
addProfiler(Class<? extends Profiler> profiler, String initLine);
-
profiler:Profiler类
-
initLine Profiler选项初始化行
-
-
addProfiler(String profiler);
-
profiler:Profiler类名称或Profiler别名
-
-
addProfiler(String profiler, String initLine);
-
profiler:Profiler类名称或Profiler别名
-
initLine Profiler选项初始化行
-
-
shouldDoGC
-
作用
-
是否在测量之间进行GC操作
-
-
参数
-
类型
boolean
,默认为false
-
result
-
作用
-
将结果写入到文件中
-
-
参数
-
写入文件的文件名,类型
String
,默认值为jmh-result
-
resultFormat
-
作用
-
指定结果写入文件的文件类型
-
-
参数
-
文件类型,类型
org.openjdk.jmh.results.format.ResultFormatType
,该类型分别为一下五种类型:TEXT、CSV、SCSV、JSON和LATEX。默认为CSV
-
output
-
作用
-
将运行日志输出
-
-
参数
-
日志写入的文件名,类型
String
-
parent
-
作用
-
覆盖给定选项中的默认值,只能使用一次。
-
-
参数
-
可基于的选项,类型
org.openjdk.jmh.runner.options.Options
-
五、测试陷阱
陷阱1:死码消除
陷阱2:常量折叠与常量传播
陷阱3:永远不要在测试中写循环
陷阱4:使用 Fork 隔离多个测试方法
陷阱5:方法内联
陷阱6:伪共享与缓存行
陷JM阱JMH7:分支预测
陷阱8:多线程测试
发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/157459.html原文链接:https://javaforall.cn
【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛
【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...