很多時(shí)候想要測(cè)試代碼運(yùn)行時(shí)間,或者比較2個(gè)運(yùn)行的效率。 最簡(jiǎn)單的方法就是Sytem.currentTimeMillis記錄2開(kāi)始和結(jié)束時(shí)間來(lái)算
但是Java 代碼越執(zhí)行越快,放在后面的方法會(huì)有優(yōu)勢(shì),這個(gè)原因受留個(gè)眼,以后研究。大概有受類(lèi)加載,緩存預(yù)熱, jit 編譯優(yōu)化等原因。
簡(jiǎn)單點(diǎn)的StopWatch
//創(chuàng)建對(duì)象
StopWatch s = new StopWatch();
//計(jì)時(shí)
s.start("這一次的名字");
....程序
//結(jié)束
s.stop();
//生成一個(gè)字符串,其中包含描述所有已執(zhí)行任務(wù)的表。
System.out.println(s.prettyPrint());
多個(gè)對(duì)象
需要有參構(gòu)造
//使用給定的 ID 構(gòu)造一個(gè)新 StopWatch 。
//當(dāng)我們有來(lái)自多個(gè)秒表的輸出并需要區(qū)分它們時(shí),該 ID 很方便。
public StopWatch(String id)
其他的應(yīng)該不太用的著
JMH
一款一款官方的微基準(zhǔn)測(cè)試工具 - JMH.
JMH(Java Microbenchmark Harness)是用于代碼微基準(zhǔn)測(cè)試的工具套件,主要是基于方法層面的基準(zhǔn)測(cè)試,精度可以達(dá)到納秒級(jí)。使用 JMH 可以讓你方便快速的進(jìn)行一次嚴(yán)格的代碼基準(zhǔn)測(cè)試,并且有多種測(cè)試模式,多種測(cè)試維度可供選擇;而且使用簡(jiǎn)單、增加注解便可啟動(dòng)測(cè)試。
加入依賴(lài)
他們說(shuō)高版本jdk自帶,我用的jdk17不知道為啥沒(méi)有,也沒(méi)有找到有教程。
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-core</artifactId>
<version>1.35</version>
</dependency>
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-generator-annprocess</artifactId>
<version>1.35</version>
</dependency>
插件
這里可以使用JMH Java Microbenchmark Harness插件快速生成。而且可以像 JUnit 一樣,運(yùn)行單獨(dú)的 Benchmark 方法\運(yùn)行類(lèi)中所有的 Benchmark 方法.
生成代碼
右邊可以直接運(yùn)行
第一個(gè)案列
將要測(cè)試的方法添加@Benchmark注解即可。
然后用main方法啟動(dòng)就可以了。
public class JMHSample_01_HelloWorld {
@Benchmark
public void wellHelloThere() {
// this method was intentionally left blank.
}
public static void main(String[] args) throws RunnerException {
Options opt = new OptionsBuilder()
.include(JMHSample_01_HelloWorld.class.getSimpleName())
.forks(1)
.build();
new Runner(opt).run();
}
}
最后的結(jié)果,前面還有一大堆,在結(jié)果進(jìn)行分析
結(jié)果分析(逐行分析)
結(jié)果輸出有這樣幾個(gè)部分
版本和參數(shù)
// JMH版本號(hào)
# JMH version: 1.35
//jdk的版本和路徑參數(shù)
# VM version: JDK 17, Java HotSpot(TM) 64-Bit Server VM, 17+35-LTS-2724
# VM invoker: D:\study\app\jdk\bin\java.exe
# VM options: -javaagent:D:\idea\IntelliJ IDEA 2021.3.3\lib\idea_rt.jar=9272:D:\idea\IntelliJ IDEA 2021.3.3\bin -Dfile.encoding=UTF-8
//黑洞模式
# Blackhole mode: compiler (auto-detected, use -Djmh.blackhole.autoDetect=false to disable)
//預(yù)熱的次數(shù)和每一次的時(shí)間
# Warmup: 5 iterations, 10 s each //5次每次10s
// 測(cè)試的次數(shù)和時(shí)間
# Measurement: 5 iterations, 10 s each
// 超時(shí),每次迭代10分鐘
# Timeout: 10 min per iteration
//線(xiàn)程
# Threads: 1 thread, will synchronize iterations
//輸出結(jié)果打印方式
# Benchmark mode: Throughput, ops/time//吞吐量
//執(zhí)行的類(lèi)
# Benchmark: org.openjdk.jmh.samples.JMHSample_01_HelloWorld.wellHelloThere
每次的統(tǒng)計(jì)
# Run progress: 0.00% complete, ETA 00:01:40
# Fork: 1 of 1
# Warmup Iteration 1: 1895950720.598 ops/s
# Warmup Iteration 2: 1906625977.172 ops/s
# Warmup Iteration 3: 2580159163.977 ops/s
# Warmup Iteration 4: 2564641382.865 ops/s
# Warmup Iteration 5: 2561493559.473 ops/s
Iteration 1: 2529900034.146 ops/s
Iteration 2: 2497119600.056 ops/s
Iteration 3: 2512703440.984 ops/s
Iteration 4: 2568341912.143 ops/s
Iteration 5: 2574424415.895 ops/s
這一部分是總體的統(tǒng)計(jì)
有最小值平均值和最大值 、標(biāo)準(zhǔn)差、置信區(qū)間
Result "org.openjdk.jmh.samples.JMHSample_01_HelloWorld.wellHelloThere":
2536497880.645 ±(99.9%) 130763529.779 ops/s [Average]
(min, avg, max) = (2497119600.056, 2536497880.645, 2574424415.895), stdev = 33958873.426
CI (99.9%): [2405734350.865, 2667261410.424] (assumes normal distribution)
一些介紹,沒(méi)啥用
# 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.
NOTE: Current JVM experimentally supports Compiler Blackholes, and they are in use. Please exercise
extra caution when trusting the results, look into the generated code to check the benchmark still
works, and factor in a small probability of new VM bugs. Additionally, while comparisons between
different JVMs are already problematic, the performance difference caused by different Blackhole
modes can be very significant. Please make sure you use the consistent Blackhole mode for comparisons.
這里比較重要,一般都會(huì)看
屬性 | 介紹 |
---|---|
benchmark | 執(zhí)行的 |
mod | 執(zhí)行模式 |
cnt | 輪次 |
score | 執(zhí)行的結(jié)果平均值 |
error | 誤差 |
units | 單位 |
Benchmark Mode Cnt Score Error Units
JMHSample_01_HelloWorld.wellHelloThere thrpt 5 2536497880.645 ± 130763529.779 ops/s
進(jìn)程已結(jié)束,退出代碼0
使用介紹
只打算簡(jiǎn)單介紹對(duì)象的配置,注解肯定用的更舒服。
對(duì)象配置
創(chuàng)建對(duì)象后在后面逐個(gè)添加屬性,然后在使用Runner類(lèi)啟動(dòng)。
Benchmark
為該方法生成基準(zhǔn)代碼,在基準(zhǔn)列表中將該方法注冊(cè)為基準(zhǔn),從注釋中讀出默認(rèn)值,并大致為基準(zhǔn)測(cè)試運(yùn)行準(zhǔn)備環(huán)境。
適用:ElementType.METHOD,后面用注解表示了
BenchmarkMode
基準(zhǔn)測(cè)試模式聲明運(yùn)行此基準(zhǔn)測(cè)試的默認(rèn)模式。有關(guān)可用的基準(zhǔn)測(cè)試模式
@Target({ElementType.METHOD, ElementType.TYPE})
Mode[] value:模式,必須的
AverageTime - 調(diào)用的平均時(shí)間
SampleTime - 隨機(jī)取樣,最后輸出取樣結(jié)果的分布,輸出會(huì)比較多,但是不會(huì)全部統(tǒng)計(jì)
SingleShotTime - 只會(huì)執(zhí)行一次,通常用來(lái)測(cè)試?yán)鋯?dòng)時(shí)候的性能。
All - 所有的benchmark modes。
Fork
執(zhí)行次數(shù),除了value默認(rèn)就好了
@Target({ElementType.METHOD,ElementType.TYPE})
int BLANK_FORKS = -1;
String BLANK_ARGS = "blank_blank_blank_2014";
/** @return 表示該benchMark執(zhí)行多少次 */
int value() default BLANK_FORKS;
/** @return 線(xiàn)束應(yīng)分叉并忽略結(jié)果的次數(shù) */
int warmups() default BLANK_FORKS;
/** @return 要運(yùn)行的 JVM 可執(zhí)行文件 */
String jvm() default BLANK_ARGS;
/** @return 要在命令行中替換的 JVM 參數(shù) */
String[] jvmArgs() default { BLANK_ARGS };
/** @return 要在命令行中預(yù)置的 JVM 參數(shù)*/
String[] jvmArgsPrepend() default { BLANK_ARGS };
/** @return :要在命令行中追加的 JVM 參數(shù)*/
String[] jvmArgsAppend() default { BLANK_ARGS };
fork(0)同一個(gè)進(jìn)程執(zhí)行一次
fork(1)新建一個(gè)進(jìn)程執(zhí)行一次
fork(n)新建n個(gè)進(jìn)程
默認(rèn)的-1等同于傳5
Warmup
允許為基準(zhǔn)設(shè)置默認(rèn)預(yù)熱參數(shù)
@Target({ElementType.METHOD,ElementType.TYPE})
/** @return 預(yù)熱迭代次數(shù) */
int iterations() default BLANK_ITERATIONS;
/** @return每次預(yù)熱迭代的時(shí)間 */
int time() default BLANK_TIME;
/** @return 預(yù)熱迭代持續(xù)時(shí)間的時(shí)間單位 */
TimeUnit timeUnit() default TimeUnit.SECONDS;
/** @return 批大?。好總€(gè)操作的基準(zhǔn)方法調(diào)用數(shù)*/
int batchSize() default BLANK_BATCHSIZE;
Measurement
設(shè)置默認(rèn)測(cè)量參數(shù)。
這個(gè)和上面一樣,就換成翻譯了
@Target({ElementType.METHOD,ElementType.TYPE})
/** @return Number of measurement iterations */
int iterations() default BLANK_ITERATIONS;
/** @return Time of each measurement iteration */
int time() default BLANK_TIME;
/** @return Time unit for measurement iteration duration */
TimeUnit timeUnit() default TimeUnit.SECONDS;
/** @return Batch size: number of benchmark method calls per operation */
int batchSize() default BLANK_BATCHSIZE;
OutputTimeUnit
為統(tǒng)計(jì)結(jié)果的時(shí)間單位
@Target({ElementType.METHOD,ElementType.TYPE})
value: 時(shí)間單位
State
狀態(tài)對(duì)象通常作為參數(shù)注入到方法中Benchmar。
@State 可以被繼承使用,如果父類(lèi)定義了該注解,子類(lèi)則無(wú)需定義。由于 JMH 允許多線(xiàn)程同時(shí)執(zhí)行測(cè)試,不同的選項(xiàng)含義如下:
@Target(ElementType.TYPE)
Scope.Benchmark:所有測(cè)試線(xiàn)程共享一個(gè)實(shí)例,測(cè)試有狀態(tài)實(shí)例在多線(xiàn)程共享下的性能
Scope.Group:同一個(gè)線(xiàn)程在同一個(gè) group 里共享實(shí)例
Scope.Thread:默認(rèn)的 State,每個(gè)測(cè)試線(xiàn)程分配一個(gè)實(shí)例
感覺(jué)這個(gè)不太好理解,這里看官方示例
這個(gè)代碼中measureShared多個(gè)線(xiàn)程會(huì)同時(shí)操作這個(gè)x。
如2線(xiàn)程輪流那么x是1 2 3 4 5 6
measureUnshared同時(shí)操作那么就是
1 1 2 2 3 3
@State(Scope.Benchmark)
public static class BenchmarkState {
volatile double x = Math.PI;
}
@State(Scope.Thread)
public static class ThreadState {
volatile double x = Math.PI;
}
@Benchmark
public void measureUnshared(ThreadState state) {
// 所有基準(zhǔn)測(cè)試線(xiàn)程都將調(diào)用此方法。
//
// 但是,由于 ThreadState 是 Scope.Thread,因此每個(gè)線(xiàn)程將擁有自己的狀態(tài)副本,此基準(zhǔn)將衡量未共享的情況。
state.x++;
}
@Benchmark
public void measureShared(BenchmarkState state) {
// 由于 BenchmarkState 是 Scope.Benchmark,所有線(xiàn)程都將共享狀態(tài)實(shí)例,我們最終將測(cè)量共享案例。
state.x++;
}
如果標(biāo)記到類(lèi),我們看第四個(gè)代碼.
那么我們相當(dāng)于可以調(diào)用這個(gè)類(lèi)的屬性,而不用注入了。
@State(Scope.Thread)
public class JMHSample_04_DefaultState {
double x = Math.PI;
@Benchmark
public void measure() {
x++;
}
Setup and TearDown
會(huì)在執(zhí)行 benchmark 之前/后被執(zhí)行,主要用于初始化/資源處理。
@Target(ElementType.METHOD)
此方法的級(jí)別。
Level value() default Level.Trial;
參數(shù)
Level.Trial:Benchmark級(jí)別,benchmark執(zhí)行完執(zhí)行,最大的。
Level.Iteration:執(zhí)行迭代級(jí)別,每次執(zhí)行完都會(huì)
Level.Invocation:每次方法調(diào)用級(jí)別,每次方法調(diào)用就都執(zhí)行
類(lèi)型 | 結(jié)果 |
---|---|
Trial | Iteration 2:x = 1.059139639E9 |
Iteration | # Warmup Iteration x = 3.54500352E8 Iteration 1: x = 7.0472257E8 Iteration 2:x = 1.059033135E9 |
Invocation | 每次都調(diào)用一下![]() |
@TearDown(Level.Iteration)
public void check() {
System.out.println("---------------------------------");
System.out.println("x = " + x);
}
@Benchmark
public void measureRight() {
x++;
}
死碼消除問(wèn)題
介紹
在第8給示例中主要介紹了死碼優(yōu)化的問(wèn)題。
如果一個(gè)方法調(diào)用后沒(méi)有什么用,就在編譯器被消除了,但這給我做基準(zhǔn)測(cè)試帶了一些麻煩
我們看第八個(gè):
講道理來(lái)演示的意思是measureWrong比rigth應(yīng)該慢超級(jí)多,但是我怎么測(cè)試都測(cè)試不出來(lái),不知道為什么。而且第九個(gè)也測(cè)試不出來(lái)。
private double x = Math.PI;
@Benchmark
public void baseline() {
// do nothing, this is a baseline
}
@Benchmark
public void measureWrong() {
// This is wrong: result is not used and the entire computation is optimized away.
Math.log(x);
}
@Benchmark
public double measureRight() {
// This is correct: the result is being used.
return Math.log(x);
}
解決
第九個(gè)示例告訴我們解決。
不過(guò)我測(cè)不出來(lái)。
- 使用他
- 注入Blackhole bh 對(duì)象,使用consume方法調(diào)用
@Benchmark
public void measureRight_2(Blackhole bh) {
bh.consume(Math.log(x1));
bh.consume(Math.log(x2));
}
同一個(gè)進(jìn)程會(huì)互相影響
眾所周知,JVM擅長(zhǎng)按配置文件優(yōu)化。這對(duì)基準(zhǔn)測(cè)試不利,因?yàn)椴煌臏y(cè)試可以將它們的配置文件混合在一起,然后為每個(gè)測(cè)試呈現(xiàn)“統(tǒng)一錯(cuò)誤”的代碼。分叉(在單獨(dú)的進(jìn)程中運(yùn)行)每個(gè)測(cè)試都有助于避免此問(wèn)題。默認(rèn)情況下,JMH 將分叉測(cè)試。
@Benchmark
@Fork(0)
public int measure_1_c1() {
return measure(c1);
}
/*
* Then Counter2...
*/
@Benchmark
@Fork(0)
public int measure_2_c2() {
return measure(c2);
}
/*
* Then Counter1 again...
*/
@Benchmark
@Fork(0)
public int measure_3_c1_again() {
return measure(c1);
}
請(qǐng)注意,C1 更快,C2 更慢,但 C1 又慢了!這是因?yàn)?C1 和 C2 的配置文件已合并在一起。請(qǐng)注意分叉運(yùn)行的測(cè)量是多么完美。文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-813059.html
還有些案例,不過(guò)不想學(xué)了,這些夠現(xiàn)在的用了,以后需要在學(xué)
文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-813059.html
到了這里,關(guān)于Java--JMH--性能測(cè)試--測(cè)試軟件運(yùn)行效率/時(shí)間--StopWatch的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!