【精通性能优化:解锁JMH微基准测试】一基本用法

文章目录

      • 1. 什么是JMH
        • 1.1 用JMH进行微基准测试
          • 1. JmhExample01.java
          • 2. 程序输出JmhExample01.java
      • 2.2 JMH的基本用法
        • 2.1 @Benchmark标记基准测试方法
        • 2.2 Warmup以及Measurement
          • 1. 设置全局的Warmup和Measurement(一)
          • 2. 设置全局的Warmup和Measurement(二)
          • 3. 在基准测试方法上设置Warmup和Measurement
        • 2.3 详解基准测试相关的输出
        • 2.4 四大BenchmarkMode
          • 1. AverageTime
          • 2. Throughput
          • 3. SampleTime
          • 4. SingleShotTime(没太懂)
          • 5. 多Mode以及All
        • 2.5 OutputTimeUnit
        • 2.6 三大State的使用
          • 1. Thread独享State
          • 2. Thread共享State
          • 3. 线程组共享State
        • 2.7 @Param的妙用
          • 1. 对比ConcurrentHashMap和SynchronizedMap的性能
          • 2. 使用@Param
        • 2.8 JMH测试套件(Fixture)
          • 1. Setup和TearDown
          • 2. Level

1. 什么是JMH

JMH是Java Micro Benchmark Harness的简写,是专门用于代码微基准测试的工具集。

JMH由实现Java虚拟你的团队开发,现代JVM已经变的越来越智能,在Java文件的编译阶段、类的加载阶段,以及运行阶段都可能进行了不同程度的优化,因此开发者编写的代码未必会像自己所预期的那样具有相同的性能体现,JMH能够让普通开发者能够了解自己所编写的代码运行的情况。

JMH GItHub

1.1 用JMH进行微基准测试

首先我们要将JMH的依赖加入我们的工程之中,这里使用最新的1.36版本

<!-- https://mvnrepository.com/artifact/org.openjdk.jmh/jmh-core -->
<dependency><groupId>org.openjdk.jmh</groupId><artifactId>jmh-core</artifactId><version>1.36</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.openjdk.jmh/jmh-generator-annprocess -->
<dependency><groupId>org.openjdk.jmh</groupId><artifactId>jmh-generator-annprocess</artifactId><version>1.36</version>
</dependency>
1. JmhExample01.java
package com.myf.concurrent.wwj2.jmh;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.openjdk.jmh.runner.options.TimeValue;import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.TimeUnit;/*** @author myf*/
@BenchmarkMode(Mode.SampleTime)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
@State(Scope.Thread)
public class JmhExample01 {private final static String DATA = "DUMMY DATA";private List<String> arrayList;private List<String> linkedList;@Setup(Level.Invocation)public void setUp(){this.arrayList = new ArrayList<>();this.linkedList = new LinkedList<>();}@Benchmarkpublic List<String> arrayListAdd(){this.arrayList.add(DATA);return arrayList;}@Benchmarkpublic List<String> linkedListAdd(){this.linkedList.add(DATA);return linkedList;}public static void main(String[] args) throws RunnerException {final Options options = new OptionsBuilder().include(JmhExample01.class.getSimpleName()).forks(1).measurementIterations(10).measurementTime(TimeValue.seconds(1)).warmupIterations(10).warmupTime(TimeValue.seconds(1)).build();new Runner(options).run();}
}

上面的程序中,我们使用了一些基本的JMH API,也许目前你还不太了解他们的用法,我们后面会逐一进行讲解。

2. 程序输出JmhExample01.java
# JMH version: 1.36
# VM version: JDK 17.0.2, Java HotSpot(TM) 64-Bit Server VM, 17.0.2+8-LTS-86
# VM invoker: /Library/Java/JavaVirtualMachines/jdk-17.0.2.jdk/Contents/Home/bin/java
# VM options: -javaagent:/Applications/IntelliJ IDEA.app/Contents/lib/idea_rt.jar=56684:/Applications/IntelliJ IDEA.app/Contents/bin -Dfile.encoding=UTF-8
# Blackhole mode: compiler (auto-detected, use -Djmh.blackhole.autoDetect=false to disable)
# Warmup: 10 iterations, 100 ms each
# Measurement: 10 iterations, 100 ms each
# Timeout: 10 min per iteration
# Threads: 1 thread, will synchronize iterations
# Benchmark mode: Average time, time/op
# Benchmark: com.myf.concurrent.wwj2.jmh.JmhExample01.arrayListAdd# Run progress: 0.00% complete, ETA 00:00:04
# Fork: 1 of 1
# Warmup Iteration   1: 63.109 ns/op
# Warmup Iteration   2: 64.557 ns/op
# Warmup Iteration   3: 29.116 ns/op
# Warmup Iteration   4: 23.864 ns/op
# Warmup Iteration   5: 23.078 ns/op
# Warmup Iteration   6: 23.269 ns/op
# Warmup Iteration   7: 24.339 ns/op
# Warmup Iteration   8: 37.417 ns/op
# Warmup Iteration   9: 37.715 ns/op
# Warmup Iteration  10: 33.296 ns/op
Iteration   1: 28.710 ns/op
Iteration   2: 23.029 ns/op
Iteration   3: 23.790 ns/op
Iteration   4: 34.254 ns/op
Iteration   5: 31.454 ns/op
Iteration   6: 30.230 ns/op
Iteration   7: 21.331 ns/op
Iteration   8: 25.501 ns/op
Iteration   9: 29.648 ns/op
Iteration  10: 38.189 ns/opResult "com.myf.concurrent.wwj2.jmh.JmhExample01.arrayListAdd":28.614 ±(99.9%) 8.007 ns/op [Average](min, avg, max) = (21.331, 28.614, 38.189), stdev = 5.296CI (99.9%): [20.606, 36.621] (assumes normal distribution)# JMH version: 1.36
# VM version: JDK 17.0.2, Java HotSpot(TM) 64-Bit Server VM, 17.0.2+8-LTS-86
# VM invoker: /Library/Java/JavaVirtualMachines/jdk-17.0.2.jdk/Contents/Home/bin/java
# VM options: -javaagent:/Applications/IntelliJ IDEA.app/Contents/lib/idea_rt.jar=56684:/Applications/IntelliJ IDEA.app/Contents/bin -Dfile.encoding=UTF-8
# Blackhole mode: compiler (auto-detected, use -Djmh.blackhole.autoDetect=false to disable)
# Warmup: 10 iterations, 100 ms each
# Measurement: 10 iterations, 100 ms each
# Timeout: 10 min per iteration
# Threads: 1 thread, will synchronize iterations
# Benchmark mode: Average time, time/op
# Benchmark: com.myf.concurrent.wwj2.jmh.JmhExample01.linkedListAdd# Run progress: 50.00% complete, ETA 00:00:03
# Fork: 1 of 1
# Warmup Iteration   1: 21.183 ns/op
# Warmup Iteration   2: 15.947 ns/op
# Warmup Iteration   3: 16.283 ns/op
# Warmup Iteration   4: 17.095 ns/op
# Warmup Iteration   5: 16.899 ns/op
# Warmup Iteration   6: 16.618 ns/op
# Warmup Iteration   7: 16.415 ns/op
# Warmup Iteration   8: 16.034 ns/op
# Warmup Iteration   9: 16.170 ns/op
# Warmup Iteration  10: 17.569 ns/op
Iteration   1: 16.021 ns/op
Iteration   2: 16.734 ns/op
Iteration   3: 16.535 ns/op
Iteration   4: 17.296 ns/op
Iteration   5: 21.086 ns/op
Iteration   6: 16.149 ns/op
Iteration   7: 16.183 ns/op
Iteration   8: 16.088 ns/op
Iteration   9: 16.659 ns/op
Iteration  10: 16.116 ns/opResult "com.myf.concurrent.wwj2.jmh.JmhExample01.linkedListAdd":16.887 ±(99.9%) 2.311 ns/op [Average](min, avg, max) = (16.021, 16.887, 21.086), stdev = 1.528CI (99.9%): [14.576, 19.197] (assumes normal distribution)# Run complete. Total time: 00:00:05REMEMBER: 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.Benchmark                   Mode  Cnt   Score   Error  Units
JmhExample01.arrayListAdd   avgt   10  28.614 ± 8.007  ns/op
JmhExample01.linkedListAdd  avgt   10  16.887 ± 2.311  ns/op

我们使用JMH基准测试,输出的信息很多,目前我们先去查看输出的最后两行,我们从这两行的信息可以发现arrayListAdd方法调用平均响应时间是28.614纳秒,误差在8.007纳秒,linkedListAdd方法的调用平均响应时间为16.887纳秒,误差在2.311纳秒,测试结果显示后者的性能是高于前者的。

2.2 JMH的基本用法

2.1 @Benchmark标记基准测试方法

与Junit4中@Test注解标记测试方法一样,JMH对基准测试的方法需要使用@Benchmark注解进行标记。

如果一个类中没有任何基准测试方法,那么对其进行基准测试将会出现异常。

删掉上述代码中两个基准测试注解将会报如下错误:

Exception in thread "main" No benchmarks to run; check the include/exclude regexps.at org.openjdk.jmh.runner.Runner.internalRun(Runner.java:258)at org.openjdk.jmh.runner.Runner.run(Runner.java:209)at com.myf.concurrent.wwj2.jmh.JmhExample02.main(JmhExample02.java:53)

2.2 Warmup以及Measurement

Warmup可以直译为“预热”的意思,Warmup做的就是在基准测试代表正式度量之前,先对其进行预热,使得度量代码的执行是经过了类的早期优化、JVM运行期编译、JIT优化之后的最终状态。

Measurement是真正的度量操作,只有度量的轮次会被计入统计之中。

1. 设置全局的Warmup和Measurement(一)

通过构造Options时设置全局的配置,也可以通过类上的注解进行设置

Options options = new OptionsBuilder().include(JmhExample02.class.getSimpleName()).forks(1)// 进行迭代测试的次数(10批次不是方法调用10次).measurementIterations(10)// 每次迭代需要的时间,默认是10S.measurementTime(TimeValue.milliseconds(100))// 进行迭代预热的次数.warmupIterations(10)// 每次迭代需要的时间,默认是10S.warmupTime(TimeValue.milliseconds(100)).build();
2. 设置全局的Warmup和Measurement(二)

通过注解指定预热和度量的配置

@Measurement(iterations = 10, time = 100, timeUnit = TimeUnit.MILLISECONDS)
@Warmup(iterations = 10, time = 100, timeUnit = TimeUnit.MILLISECONDS)
public class JmhExample01 {
3. 在基准测试方法上设置Warmup和Measurement

注解也可以设置在基准测试方法上,只对当前方法生效。

@Benchmark
@Measurement(iterations = 5, time = 100, timeUnit = TimeUnit.MILLISECONDS)
@Warmup(iterations = 5, time = 100, timeUnit = TimeUnit.MILLISECONDS)
public List<String> arrayListAdd() {this.arrayList.add(DATA);return arrayList;
}

经测试,当前1.36版本基准测试方法上的注解可以覆盖类上的注解配置,无法覆盖Option中的配置

2.3 详解基准测试相关的输出

# 使用的JMH版本
# JMH version: 1.36
# 下面是JDK的版本信息
# VM version: JDK 17.0.2, Java HotSpot(TM) 64-Bit Server VM, 17.0.2+8-LTS-86
# Java命令的目录
# VM invoker: /Library/Java/JavaVirtualMachines/jdk-17.0.2.jdk/Contents/Home/bin/java
# JVM运行时指定的参数
# VM options: -javaagent:/Applications/IntelliJ IDEA.app/Contents/lib/idea_rt.jar=54283:/Applications/IntelliJ IDEA.app/Contents/bin -Dfile.encoding=UTF-8
# Blackhole mode: compiler (auto-detected, use -Djmh.blackhole.autoDetect=false to disable)
# 热身的批次为10,每一个批次都将会不断地调用arrayListAdd方法,每一个批次的执行时间为100ms
# Warmup: 10 iterations, 100 ms each
# 真正度量的批次为10,这10个批次的调用产生的性能数据才会真正地纳入统计中,同样每一个批次的度量执行的时间也为100ms
# Measurement: 10 iterations, 100 ms each
# 每一个批次的超时时间
# Timeout: 10 min per iteration
# 执行基准测试的线程数量
# Threads: 1 thread, will synchronize iterations
# Benchmark的Mode,这里表明统计的方法是方法调用一次所耗费的单位时间。
# Benchmark mode: Average time, time/op
# 当前测试方法的绝对路径
# Benchmark: com.myf.concurrent.wwj2.jmh.JmhExample01.arrayListAdd# 执行进度
# Run progress: 0.00% complete, ETA 00:00:04
# Fork: 1 of 1
# 执行十个批次的热身,第一批次调用方法的平均耗时为35.756纳秒,第二批次调用方法的平均耗时为24.443纳秒
# Warmup Iteration   1: 35.756 ns/op
# Warmup Iteration   2: 24.443 ns/op
# Warmup Iteration   3: 28.334 ns/op
# Warmup Iteration   4: 35.592 ns/op
# Warmup Iteration   5: 22.711 ns/op
# Warmup Iteration   6: 25.595 ns/op
# Warmup Iteration   7: 28.407 ns/op
# Warmup Iteration   8: 37.375 ns/op
# Warmup Iteration   9: 39.011 ns/op
# Warmup Iteration  10: 33.058 ns/op
# 执行十个批次的度量
Iteration   1: 26.739 ns/op
Iteration   2: 22.633 ns/op
Iteration   3: 24.204 ns/op
Iteration   4: 20.832 ns/op
Iteration   5: 20.575 ns/op
Iteration   6: 20.881 ns/op
Iteration   7: 21.091 ns/op
Iteration   8: 20.630 ns/op
Iteration   9: 21.314 ns/op
Iteration  10: 22.101 ns/op# 。。。略略略。。。# 最终的统计结果
Benchmark                   Mode  Cnt   Score   Error  Units
JmhExample01.arrayListAdd   avgt   10  22.100 ± 3.000  ns/op
JmhExample01.linkedListAdd  avgt   10  16.364 ± 1.325  ns/op

2.4 四大BenchmarkMode

JMH使用@BenchmarkMode这个注解来声明使用哪一种模式来运行,JMH为我们提供了四种运行模式。

JMH允许若干个模式同时存在。

@BenchmarkMode注解可设置在类上也可以设置在基准方法上,即准方法上的设置会覆盖类上的设置,也可以在Option中通过.mode()方法设置,基准测试方法无法覆盖Option中的设置。

1. AverageTime

平均响应时间:输出基准测试方法每次调用所耗费的时间

Benchmark                   Mode  Cnt   Score   Error  Units
JmhExample01.arrayListAdd   avgt   10  22.100 ± 3.000  ns/op
JmhExample01.linkedListAdd  avgt   10  16.364 ± 1.325  ns/op
2. Throughput

方法吞吐量:输出信息表明在单位时间内可以对该方法调用多少次

本次测试我调整了这个为微妙@OutputTimeUnit(TimeUnit.MICROSECONDS)

Benchmark                    Mode  Cnt   Score   Error   Units
JmhExample01.arrayListAdd   thrpt   10  48.007 ± 1.863  ops/us
JmhExample01.linkedListAdd  thrpt   10  62.169 ± 1.439  ops/us
3. SampleTime

时间采样:采用一种抽样的方式来统计基准测试方法的性能结果,他会收集所有的性能数据,并将其分布在不同的区间中。

从输出结果可以发现,对arrayListAdd进行了29951次调用,平均响应时间为0.244微妙,总共有29920的数据点落在了0-50微妙这个区间之中

# 。。。略略略。。。
Result "com.myf.concurrent.wwj2.jmh.JmhExample01.arrayListAdd":N = 29951mean =      0.244 ±(99.9%) 0.131 us/opHistogram, us/op:[  0.000,  50.000) = 29920 [ 50.000, 100.000) = 15 [100.000, 150.000) = 8 [150.000, 200.000) = 2 [200.000, 250.000) = 0 [250.000, 300.000) = 1 [300.000, 350.000) = 2 [350.000, 400.000) = 1 [400.000, 450.000) = 0 [450.000, 500.000) = 1 [500.000, 550.000) = 0 [550.000, 600.000) = 0 [600.000, 650.000) = 0 Percentiles, us/op:p(0.0000) =0 us/opp(50.0000) =      0.042 us/opp(90.0000) =      0.083 us/opp(95.0000) =      0.125 us/opp(99.0000) =      0.250 us/opp(99.9000) =     51.807 us/opp(99.9900) =    364.157 us/opp(99.9990) =    679.936 us/opp(99.9999) =    679.936 us/opp(100.0000) =    679.936 us/op# 。。。略略略。。。Benchmark                                           Mode    Cnt    Score   Error  Units
JmhExample01.arrayListAdd                         sample  29951    0.244 ± 0.131  us/op
JmhExample01.arrayListAdd:arrayListAdd·p0.00      sample             ≈ 0          us/op
JmhExample01.arrayListAdd:arrayListAdd·p0.50      sample           0.042          us/op
JmhExample01.arrayListAdd:arrayListAdd·p0.90      sample           0.083          us/op
JmhExample01.arrayListAdd:arrayListAdd·p0.95      sample           0.125          us/op
JmhExample01.arrayListAdd:arrayListAdd·p0.99      sample           0.250          us/op
JmhExample01.arrayListAdd:arrayListAdd·p0.999     sample          51.807          us/op
JmhExample01.arrayListAdd:arrayListAdd·p0.9999    sample         364.157          us/op
JmhExample01.arrayListAdd:arrayListAdd·p1.00      sample         679.936          us/op
JmhExample01.linkedListAdd                        sample  26853    0.023 ± 0.003  us/op
JmhExample01.linkedListAdd:linkedListAdd·p0.00    sample             ≈ 0          us/op
JmhExample01.linkedListAdd:linkedListAdd·p0.50    sample             ≈ 0          us/op
JmhExample01.linkedListAdd:linkedListAdd·p0.90    sample           0.042          us/op
JmhExample01.linkedListAdd:linkedListAdd·p0.95    sample           0.042          us/op
JmhExample01.linkedListAdd:linkedListAdd·p0.99    sample           0.042          us/op
JmhExample01.linkedListAdd:linkedListAdd·p0.999   sample           0.125          us/op
JmhExample01.linkedListAdd:linkedListAdd·p0.9999  sample          11.186          us/op
JmhExample01.linkedListAdd:linkedListAdd·p1.00    sample          17.312          us/op
4. SingleShotTime(没太懂)

冷测试:无论是Warmup还是Measurement在每一个批次中基准测试方法只会被执行一次,一般我们会讲Warmup的批次设置为0。

# 。。。略略略。。。Result "com.myf.concurrent.wwj2.jmh.JmhExample01.arrayListAdd":N = 10mean =      1.421 ±(99.9%) 0.986 us/opHistogram, us/op:[0.000, 0.250) = 0 [0.250, 0.500) = 0 [0.500, 0.750) = 0 [0.750, 1.000) = 4 [1.000, 1.250) = 2 [1.250, 1.500) = 1 [1.500, 1.750) = 0 [1.750, 2.000) = 1 [2.000, 2.250) = 0 [2.250, 2.500) = 1 [2.500, 2.750) = 1 Percentiles, us/op:p(0.0000) =      0.875 us/opp(50.0000) =      1.125 us/opp(90.0000) =      2.638 us/opp(95.0000) =      2.667 us/opp(99.0000) =      2.667 us/opp(99.9000) =      2.667 us/opp(99.9900) =      2.667 us/opp(99.9990) =      2.667 us/opp(99.9999) =      2.667 us/opp(100.0000) =      2.667 us/op# 。。。略略略。。。Benchmark                   Mode  Cnt  Score   Error  Units
JmhExample01.arrayListAdd     ss   10  1.421 ± 0.986  us/op
JmhExample01.linkedListAdd    ss   10  1.817 ± 0.904  us/op
5. 多Mode以及All

可以设置多个模式的方法运行基准测试方法。

@BenchmarkMode({Mode.AverageTime, Mode.Throughput})
Benchmark                    Mode  Cnt   Score   Error   Units
JmhExample01.arrayListAdd   thrpt   10  44.052 ± 6.503  ops/us
JmhExample01.linkedListAdd  thrpt   10  62.376 ± 1.905  ops/us
JmhExample01.arrayListAdd    avgt   10   0.027 ± 0.016   us/op
JmhExample01.linkedListAdd   avgt   10   0.017 ± 0.002   us/op

甚至可以设置全部Mode

@BenchmarkMode(Mode.All)
Benchmark                                           Mode    Cnt   Score   Error   Units
JmhExample01.arrayListAdd                          thrpt     10  45.572 ± 2.751  ops/us
JmhExample01.linkedListAdd                         thrpt     10  62.004 ± 2.739  ops/us
JmhExample01.arrayListAdd                           avgt     10   0.022 ± 0.001   us/op
JmhExample01.linkedListAdd                          avgt     10   0.016 ± 0.001   us/op
JmhExample01.arrayListAdd                         sample  24399   0.032 ± 0.014   us/op
JmhExample01.arrayListAdd:arrayListAdd·p0.00      sample            ≈ 0           us/op
JmhExample01.arrayListAdd:arrayListAdd·p0.50      sample          0.041           us/op
JmhExample01.arrayListAdd:arrayListAdd·p0.90      sample          0.042           us/op
JmhExample01.arrayListAdd:arrayListAdd·p0.95      sample          0.042           us/op
JmhExample01.arrayListAdd:arrayListAdd·p0.99      sample          0.125           us/op
JmhExample01.arrayListAdd:arrayListAdd·p0.999     sample          0.817           us/op
JmhExample01.arrayListAdd:arrayListAdd·p0.9999    sample         13.082           us/op
JmhExample01.arrayListAdd:arrayListAdd·p1.00      sample         99.328           us/op
JmhExample01.linkedListAdd                        sample  26468   0.023 ± 0.001   us/op
JmhExample01.linkedListAdd:linkedListAdd·p0.00    sample            ≈ 0           us/op
JmhExample01.linkedListAdd:linkedListAdd·p0.50    sample          0.041           us/op
JmhExample01.linkedListAdd:linkedListAdd·p0.90    sample          0.042           us/op
JmhExample01.linkedListAdd:linkedListAdd·p0.95    sample          0.042           us/op
JmhExample01.linkedListAdd:linkedListAdd·p0.99    sample          0.042           us/op
JmhExample01.linkedListAdd:linkedListAdd·p0.999   sample          0.272           us/op
JmhExample01.linkedListAdd:linkedListAdd·p0.9999  sample          3.668           us/op
JmhExample01.linkedListAdd:linkedListAdd·p1.00    sample          6.288           us/op
JmhExample01.arrayListAdd                             ss     10   0.583 ± 0.165   us/op
JmhExample01.linkedListAdd                            ss     10   1.100 ± 1.036   us/op

2.5 OutputTimeUnit

OutputTimeUnit提供了统计结果输出时的时间单位

2.6 三大State的使用

State用于标记状态对象,状态对象封装了基准测试处理的对象的状态,状态对象的作用域(scope)定义了它在工作线程之间共享的状态。

状态对象通常作为参数注入Benchmark方法,JMH负责状态对象的实例化和共享。状态对象也可以被注入到其他状态对象的Setup和TearDown方法中,以或得阶段初始化。State-s之间的依赖关系图应该是有向无环图。

State对象可以被继承:您可以将State放在一个超类上,并使用子类作为状态。

1. Thread独享State

每一个运行基准测试方法的线程都会持有一个独立的对象实例,该实例既有可能是作为基准测试方法参数传入的,也有可能是运行基准测试方法所在的宿主class(前面测试用例我们在类上就加了相关配置),将State设置为Scope.Thread一般主要是针对非线程安全的类。

package com.myf.concurrent.wwj2.jmh;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 java.util.concurrent.TimeUnit;/*** @author myf*/
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Fork(1)
@Warmup(iterations = 5, time = 100, timeUnit = TimeUnit.MILLISECONDS)
@Measurement(iterations = 10, time = 100, timeUnit = TimeUnit.MILLISECONDS)
// 设置5个线程运行基准测试方法
@Threads(5)
public class JmhExample06 {// 5个运行线程,每一个线程都会持有一个Test实例@State(Scope.Thread)public static class Test{public Test() {System.out.println("create Test instance");}public void method(){}}// JMH将状态对象Test作为参数引入@Benchmarkpublic void test(Test test){test.method();}public static void main(String[] args) throws RunnerException {final Options options = new OptionsBuilder().include(JmhExample06.class.getSimpleName()).build();new Runner(options).run();}}

运行上面的程序,我们会看到"create Test instance"字样出现了5次。即每一个线程都会持有单独的Test实例。

# 。。。略略略。。。
# Benchmark: com.myf.concurrent.wwj2.jmh.JmhExample06.test# Run progress: 0.00% complete, ETA 00:00:01
# Fork: 1 of 1
# Warmup Iteration   1: create Test instance
create Test instance
create Test instance
create Test instance
create Test instance
599.513 ±(99.9%) 1401.639 ns/op
# Warmup Iteration   2: 7.567 ±(99.9%) 4.938 ns/op
# Warmup Iteration   3: 3.152 ±(99.9%) 2.982 ns/op
# Warmup Iteration   4: 1.401 ±(99.9%) 0.911 ns/op
# Warmup Iteration   5: 1.688 ±(99.9%) 1.052 ns/op# 。。。略略略。。。
2. Thread共享State

我们需要测试多线程的情况下某个类被不同线程操作时的性能,JMH提供了对县城共享的一种状态Scope.Benchmark

package com.myf.concurrent.wwj2.jmh;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 java.util.concurrent.TimeUnit;/*** @author myf*/
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Fork(1)
@Warmup(iterations = 5, time = 100, timeUnit = TimeUnit.MILLISECONDS)
@Measurement(iterations = 10, time = 100, timeUnit = TimeUnit.MILLISECONDS)
// 设置5个线程运行基准测试方法
@Threads(5)
public class JmhExample07 {// Test实例将会被多个线程共享@State(Scope.Benchmark)public static class Test{public Test() {System.out.println("create Test instance");}public void method(){}}// JMH将状态对象Test作为参数引入@Benchmarkpublic void test(Test test){test.method();}public static void main(String[] args) throws RunnerException {final Options options = new OptionsBuilder().include(JmhExample07.class.getSimpleName()).build();new Runner(options).run();}}

运行上面的程序,我们会看到"create Test instance"字样出现了1次。即Test实例将会被多个线程共享。

# 。。。略略略。。。# Benchmark: com.myf.concurrent.wwj2.jmh.JmhExample07.test# Run progress: 0.00% complete, ETA 00:00:01
# Fork: 1 of 1
# Warmup Iteration   1: create Test instance
6.185 ±(99.9%) 11.608 ns/op
# Warmup Iteration   2: 0.833 ±(99.9%) 0.245 ns/op
# Warmup Iteration   3: 1.544 ±(99.9%) 0.418 ns/op
# Warmup Iteration   4: 1.573 ±(99.9%) 0.917 ns/op# 。。。略略略。。。
3. 线程组共享State

前面的实例中,我们所编写的基准测试方法都会被JMH框架根据方法名的字典顺序排序后逐个调用执行,因此不存在两个方法同时运行的情况。

如果我们想测试如下问题,第一,在多线程情况下的单个实例;第二允许一个以上的基准测试方法并行地运行。

这时候我们就要用到Scope.Group

package com.myf.concurrent.wwj2.jmh;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 java.util.concurrent.TimeUnit;/*** @author myf*/
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Fork(1)
@Warmup(iterations = 1, time = 100, timeUnit = TimeUnit.MILLISECONDS)
@Measurement(iterations = 5, time = 100, timeUnit = TimeUnit.MILLISECONDS)
public class JmhExample08 {// Test实例将会被设置为线程组共享的@State(Scope.Group)public static class Test{public Test() {System.out.println("create Test instance");}public void write(){System.out.println("write");}public void read(){System.out.println("read");}}// 在线程组test中,有三个线程将不断对Test实例的read方法进行调用@GroupThreads(3)@Group("test")@Benchmarkpublic void testRead(Test test){test.read();}// 在线程组test中,有三个线程将不断对Test实例的write方法进行调用@GroupThreads(3)@Group("test")@Benchmarkpublic void testWrite(Test test){test.write();}public static void main(String[] args) throws RunnerException {final Options options = new OptionsBuilder().include(JmhExample08.class.getSimpleName()).build();new Runner(options).run();}}

执行上述代码,我们会发现read和write分别交替输出。

# 。。。略略略。。。
# Timeout: 10 min per iteration
# 总共6个线程会执行基准测试方法,这6个线程在同一个Group中
# Threads: 6 threads (1 group; 3x "testRead", 3x "testWrite" in each group), will synchronize iterations
# Benchmark mode: Average time, time/op
# Benchmark: com.myf.concurrent.wwj2.jmh.JmhExample08.test# Run progress: 0.00% complete, ETA 00:00:00
# Fork: 1 of 1
# Warmup Iteration   1: create Test instance# 。。。略略略。。。
# read和write分别交替输出
read
read
read
read
write
write
write
read
read
read
read
# 。。。略略略。。。Benchmark                    Mode  Cnt      Score        Error  Units
JmhExample08.test            avgt    5  84965.982 ±  71625.520  ns/op
JmhExample08.test:testRead   avgt    5  92158.539 ± 147051.981  ns/op
JmhExample08.test:testWrite  avgt    5  77773.425 ± 111967.836  ns/op

2.7 @Param的妙用

假设你在编写代码的过程中需要用到一个Map容器,第一,需要保证使用过程中线程的安全性,第二,该容器需要有比较好的性能,比如,执行put方法最快,执行get方法最快等。作为Java程序员,JDK可供我们选择的方案其实有不少,比如ConcurrentHashMap、Hashtable、ConcurrentSkipListMap以及SynchronizedMap等,虽然它们都能够保证在多线程操作下的数据一致性,但是各自的性能表现又是怎样的呢?这就需要我们对其进行微基准测试(我们的测试相对来说比较片面,只在多线程的情况下对其进行put操作,也就是说并未涉及读取以及删除的操作)。

1. 对比ConcurrentHashMap和SynchronizedMap的性能

根据前面所学知识我们可以轻松写出满足该对比需求的基准测试。

package com.myf.concurrent.wwj2.jmh;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 java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;@BenchmarkMode(Mode.AverageTime)
@Fork(1)
@Warmup(iterations = 5, time = 100, timeUnit = TimeUnit.MILLISECONDS)
@Measurement(iterations = 10, time = 100, timeUnit = TimeUnit.MILLISECONDS)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
// 5个线程同事对共享资源进行操作
@Threads(5)
// 将状态对象JmhExample09设置为线程之间共享资源
@State(Scope.Benchmark)
public class JmhExample09 {private Map<Long, Long> concurrentMap;private Map<Long, Long> synchnoizedMap;@Setuppublic void setUp(){concurrentMap = new ConcurrentHashMap<>();synchnoizedMap = Collections.synchronizedMap(new HashMap<>());}@Benchmarkpublic void testConcurrentMap(){this.concurrentMap.put(System.nanoTime(), System.nanoTime());}@Benchmarkpublic void testSynchnoizedMap(){this.synchnoizedMap.put(System.nanoTime(), System.nanoTime());}public static void main(String[] args) throws RunnerException {final Options options = new OptionsBuilder().include(JmhExample09.class.getSimpleName()).build();new Runner(options).run();}
}

这里设置测试方法本身为Thread共享State,用五个线程进行测试,输出结果如下:

Benchmark                        Mode  Cnt      Score       Error  Units
JmhExample09.testConcurrentMap   avgt   10   1630.001 ±  1260.341  ns/op
JmhExample09.testSynchnoizedMap  avgt   10  13360.557 ± 38052.348  ns/op

从结果我们可以看出,ConcurrentHashMap的写性能要优于synchronizedMap。

2. 使用@Param

Java提供的具备线程安全的Map接口实现并非只有ConcurrentHashMap和SynchronizedMap,同样,ConcurrentSkipListMap和Hashtable也可供我们选择,如果我们要对其进行测试,那么这里需要再增加两个不同类型的Map和两个针对这两个Map实现的基准测试方法。但是很显然,这种方式存在大量的代码冗余,因此JMH为我们提供了一个@Param的注解,它使得参数可配置,也就是说一个参数在每一次的基准测试时都会有不同的值与之对应。

package com.myf.concurrent.wwj2.jmh;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 java.util.Collections;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentSkipListMap;
import java.util.concurrent.TimeUnit;@BenchmarkMode(Mode.AverageTime)
@Fork(1)
@Warmup(iterations = 5, time = 100, timeUnit = TimeUnit.MILLISECONDS)
@Measurement(iterations = 10, time = 100, timeUnit = TimeUnit.MILLISECONDS)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
// 5个线程同事对共享资源进行操作
@Threads(5)
// 将状态对象JmhExample09设置为线程之间共享资源
@State(Scope.Benchmark)
public class JmhExample10 {@Param({"1", "2", "3", "4"})private int type;private Map<Long, Long> map;@Setuppublic void setUp() {switch (type) {case 1 -> this.map = new ConcurrentHashMap<>();case 2 -> this.map = new ConcurrentSkipListMap<>();case 3 -> this.map = new Hashtable<>();case 4 -> this.map = Collections.synchronizedMap(new HashMap<>());default -> throw new IllegalArgumentException("Illegal map type.");}}@Benchmarkpublic void testConcurrentMap() {this.map.put(System.nanoTime(), System.nanoTime());}public static void main(String[] args) throws RunnerException {final Options options = new OptionsBuilder().include(JmhExample10.class.getSimpleName()).build();new Runner(options).run();}
}

引进了@Param对变量的可配置化,因此我们只需要写一个基准测试方法即可,JMH会根据@Param所提供的参数值,对test方法分别进行基准测试的运行与统计,这样我们就不需要为每一个map容器都写一个基准测试方法了。

Benchmark                       (type)  Mode  Cnt     Score      Error  Units
JmhExample10.testConcurrentMap       1  avgt   10  1905.870 ± 1855.904  ns/op
JmhExample10.testConcurrentMap       2  avgt   10   918.250 ±  267.272  ns/op
JmhExample10.testConcurrentMap       3  avgt   10  2333.470 ±  689.124  ns/op
JmhExample10.testConcurrentMap       4  avgt   10  4950.044 ± 3697.307  ns/op

运行上面的基准测试,我们会发现输出结果中多了type这样一列信息。

2.8 JMH测试套件(Fixture)

1. Setup和TearDown

JMH提供了两个注解@Setup和@TearDown用于套件测试。

@Setup会在每一个基准测试方法执行前被调用,通常用于资源的初始化。

@TearDown会在基准测试方法被执行之后被调用,通常用于资源的回收清理工作。

默认情况下@Setup和@TearDown会在一个基准方法的所有批次执行前后分别执行

这里我们只需要对JmhExample10,做出以下调整,观察变化

@Setup
public void setUp() {System.out.println("setUp执行了" + Thread.currentThread().getName());switch (type) {case 1 -> this.map = new ConcurrentHashMap<>();case 2 -> this.map = new ConcurrentSkipListMap<>();case 3 -> this.map = new Hashtable<>();case 4 -> this.map = Collections.synchronizedMap(new HashMap<>());default -> throw new IllegalArgumentException("Illegal map type.");}
}@TearDown
public void tearDown() {System.out.println("tearDown执行了" + Thread.currentThread().getName());
}

我们会发现每一个基准测试方法的输出发生了如下变化

# Run progress: 0.00% complete, ETA 00:00:06
# Fork: 1 of 1
# Warmup Iteration   1: setUp执行了com.myf.concurrent.wwj2.jmh.JmhExample10.testConcurrentMap-jmh-worker-1
1825.038 ±(99.9%) 2807.819 ns/op
# Warmup Iteration   2: 1423.850 ±(99.9%) 1637.913 ns/op
# Warmup Iteration   3: 1267.709 ±(99.9%) 1272.636 ns/op
# Warmup Iteration   4: 1475.872 ±(99.9%) 2460.539 ns/op
# Warmup Iteration   5: 896.906 ±(99.9%) 737.925 ns/op
Iteration   1: 1433.655 ±(99.9%) 1074.316 ns/op
Iteration   2: 2235.900 ±(99.9%) 3502.619 ns/op
Iteration   3: 2860.355 ±(99.9%) 4995.927 ns/op
Iteration   4: 964.292 ±(99.9%) 1568.305 ns/op
Iteration   5: 3285.678 ±(99.9%) 17619.895 ns/op
Iteration   6: 8562.194 ±(99.9%) 66921.642 ns/op
Iteration   7: 1234.296 ±(99.9%) 1633.239 ns/op
Iteration   8: 5399.636 ±(99.9%) 16028.597 ns/op
Iteration   9: 4187.621 ±(99.9%) 7297.895 ns/op
Iteration  10: tearDown执行了com.myf.concurrent.wwj2.jmh.JmhExample10.testConcurrentMap-jmh-worker-4
3684.771 ±(99.9%) 6960.884 ns/op
2. Level

如果需要再每一个批次或者每一次基准测试方法调用执行的前后执行对应的套件方法,则需要对@Setup和@TearDown进行简单的配置。

  • Trial Setup和TearDown默认的配置,该套件方法会在每一个基准测试方法的所有批次执行的前后被执行。

    @Setup(Level.Trial)
    
  • Iteration:由于我们可以设置Warmup和Measurement,因此每一个基准测试方法都会被执行若干个批次,如果想要在每一个基准测试批次执行的前后调用套件方法,则可以将Level设置为Iteration。

    Warmup和Measurement设置的10批次不是方法调用10次

    @Setup(Level.Iteration)
    
  • Invocation:将Level设置为Invocation意味着在每一个批次的度量过程中,每一次对基准方法的调用前后都会执行套件方法。

    Warmup和Measurement设置的10批次不是方法调用10次

    @Setup(Level.Invocation)
    

    执行结果

    setUp执行了com.myf.concurrent.wwj2.jmh.JmhExample10.testConcurrentMap-jmh-worker-5
    setUp执行了com.myf.concurrent.wwj2.jmh.JmhExample10.testConcurrentMap-jmh-worker-5
    setUp执行了com.myf.concurrent.wwj2.jmh.JmhExample10.testConcurrentMap-jmh-worker-5
    setUp执行了com.myf.concurrent.wwj2.jmh.JmhExample10.testConcurrentMap-jmh-worker-5
    setUp执行了com.myf.concurrent.wwj2.jmh.JmhExample10.testConcurrentMap-jmh-worker-5
    setUp执行了com.myf.concurrent.wwj2.jmh.JmhExample10.testConcurrentMap-jmh-worker-5
    setUp执行了com.myf.concurrent.wwj2.jmh.JmhExample10.testConcurrentMap-jmh-worker-5
    setUp执行了com.myf.concurrent.wwj2.jmh.JmhExample10.testConcurrentMap-jmh-worker-5
    tearDown执行了com.myf.concurrent.wwj2.jmh.JmhExample10.testConcurrentMap-jmh-worker-2
    3740.522 ±(99.9%) 2590.521 ns/op
    

需要注意的是,套件方法的执行也会产生CPU时间的消耗,但是JMH并不会将这部分时间纳入基准方法的统计之中,这一点更进一步地说明了JMH的严谨之处。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/44588.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

设计模式篇---抽象工厂(包含优化)

文章目录 概念结构实例优化 概念 抽象工厂&#xff1a;提供一个创建一系列相关或相互依赖对象的接口&#xff0c;而无须指定它们具体的类。 工厂方法是有一个类型的产品&#xff0c;也就是只有一个产品的抽象类或接口&#xff0c;而抽象工厂相对于工厂方法来说&#xff0c;是有…

Android Studio实现解析HTML获取图片URL,将URL存到list,进行列表展示

目录 效果build.gradle(app)添加的依赖(用不上的可以不加)AndroidManifest.xml错误代码activity_main.xmlitem_image.xmlMainActivityImage适配器ImageModel 接收图片URL效果 build.gradle(app)添加的依赖(用不上的可以不加) dependencies {implementation com.square…

Python可视化在量化交易中的应用(11)_Seaborn折线图

举个栗子&#xff0c;用seaborn绘制折线图。 Seaborn中折线图的绘制方法 在seaborn中&#xff0c;我们一般使用sns作为seaborn模块的别名&#xff0c;因此&#xff0c;在下文中&#xff0c;均以sns指代seaborn模块。 seaborn中绘制折线图使用的是sns.lineplot()函数&#xff…

springboot+grpc+k8s+istio环境

2023年8月17日&#xff0c;本人之前使用过nacosdubbospringboot、eurekafeign等环境。最近学习到了istio服务网格集成到k8s也可以实现分布式微服务。 1. 环境 Kubernetes集群istio集成到k8sjdk17 (8也ok)gPRC服务间通信 2. 微服务 cloud-config服务是spring-cloud-config-s…

中国剩余定理及扩展

目录 中国剩余定理解释 中国剩余定理扩展——求解模数不互质情况下的线性方程组&#xff1a; 代码实现&#xff1a; 互质&#xff1a; 非互质&#xff1a; 中国剩余定理解释 在《孙子算经》中有这样一个问题&#xff1a;“今有物不知其数&#xff0c;三三数之剩二&#x…

【C++】做一个飞机空战小游戏(十)——子弹击落炮弹、炮弹与飞机相撞

[导读]本系列博文内容链接如下&#xff1a; 【C】做一个飞机空战小游戏(一)——使用getch()函数获得键盘码值 【C】做一个飞机空战小游戏(二)——利用getch()函数实现键盘控制单个字符移动【C】做一个飞机空战小游戏(三)——getch()函数控制任意造型飞机图标移动 【C】做一个飞…

leetcode做题笔记88. 合并两个有序数组

给你两个按 非递减顺序 排列的整数数组 nums1 和 nums2&#xff0c;另有两个整数 m 和 n &#xff0c;分别表示 nums1 和 nums2 中的元素数目。 请你 合并 nums2 到 nums1 中&#xff0c;使合并后的数组同样按 非递减顺序 排列。 注意&#xff1a;最终&#xff0c;合并后数组…

Mybatis的SqlSource SqlNode BoundSql

学习链接 MyBatis SqlSource解析 【Mybatis】Mybatis源码之SqlSource#getBoundSql获取预编译SQL Mybatis中SqlSource解析流程详解 Mybatis TypeHandler解析 图解 Mybatis的SqlSource&SqlNode - processon DynamicSqlSource public class DynamicSqlSource implement…

notepad++ verilog关键字自动补全

新建verilog.xml放在安装目录下 D:\Program Files (x86)\Notepad\autoCompletion <?xml version"1.0" encoding"Windows-1252" ?> <NotepadPlus><AutoComplete><KeyWord name"accept_on" /><KeyWord name"a…

mysql自定义实体类框架

根据表结构自动生产实体类和方法,根据反射与io生成,可自定义扩展方法 package com.digital.web.front; /*** pom依赖* <dependency>* <groupId>mysql</groupId>* <artifactId>mysql-connector-java</artifactId>* <version>5.1.27</ve…

设计模式详解-责任链模式

类型&#xff1a;行为型模式 实现原理&#xff1a;为请求创建了一个接收者对象的链。对请求的发送者和接收者进行解耦&#xff0c;每个接收者都包含对另一个接收者的引用&#xff0c;如果一个对象不能处理该请求&#xff0c;那么它会把相同的请求传给下一个接收者&#xff0c;…

极致鸿蒙2.0——华为MatePad系列安装AidLux,一个自带vscode的Python编译环境

看着刚刚人入手的华为鸿蒙系统MatePad11平板&#xff0c;是如此的美轮美奂&#xff0c;但是总感觉少了点什么&#xff0c;少了点什么呢&#xff1f;是编程环境&#xff0c;我爱MatePad&#xff0c;也爱编程&#xff0c;那如果可以在MatePad上编程&#xff0c;会发生什么&#x…

SpringBoot、Java 使用 Jsoup 解析 HTML 页面

使用 Jsoup 解析 HTML 页面 什么是 Jsoup&#xff1f; Jsoup 是一个用于处理 HTML 页面的 Java 库&#xff0c;它提供了简单的 API&#xff0c;使得从 HTML 中提取数据变得非常容易。无论是获取特定标签的内容还是遍历整个页面的元素&#xff0c;Jsoup 都能轻松胜任。 如何使…

【ARM Linux 系统稳定性分析入门及渐进12 -- GDB内存查看命令 “x“(examine)】

文章目录 gdb 内存查看命令 examine 上篇文章&#xff1a;ARM Linux 系统稳定性分析入门及渐进11 – GDB( print 和 p 的使用| 和 &#xff1a;&#xff1a;的使用|ptype|{&#xff1c;type&#xff1e;} &#xff1c;addr&#xff1e; ) gdb 内存查看命令 examine examine是…

c语言——判断,判断是否是字母

//判断&#xff0c;判断是否是字母 #include<stdio.h> #include<stdlib.h> int main() {char c;printf("输入字符&#xff1a;");scanf("%c",&c);if((c>a&&c<z)||(c>A&&c<Z)) //a~z的ASCLL区间是97-122&…

【每日一题Day299】LC2235两整数相加

两整数相加【LC2235】 给你两个整数 num1 和 num2&#xff0c;返回这两个整数的和。 实现 class Solution {public int sum(int num1, int num2) {return num1 num2;} }复杂度 时间复杂度&#xff1a; O ( 1 ) \mathcal{O}(1) O(1)空间复杂度&#xff1a; O ( 1 ) \mathcal{O}…

Unity进阶–通过PhotonServer实现联网登录注册功能(客户端)–PhotonServer(三)

文章目录 Unity进阶–通过PhotonServer实现联网登录注册功能(客户端)–PhotonServer(三)前情提要客户端部分 Unity进阶–通过PhotonServer实现联网登录注册功能(客户端)–PhotonServer(三) 前情提要 单例泛型类 using System.Collections; using System.Collections.Generic; …

【C# 基础精讲】自定义异常类

自定义异常类是C#中异常处理的一种重要方式&#xff0c;它允许您创建具有自定义错误信息和处理逻辑的异常类型&#xff0c;以提高程序的可读性和可维护性。通过自定义异常类&#xff0c;您可以为特定的业务逻辑或应用场景创建更有意义的异常&#xff0c;使错误处理更加精确和有…

【Go 基础篇】Go语言关键字和预定义标识符解析:探索编程的基石与核心要素

介绍 在计算机编程中&#xff0c;关键字&#xff08;Keywords&#xff09;和预定义标识符&#xff08;Predefined Identifiers&#xff09;是编程语言的核心要素&#xff0c;它们在语法结构和语言功能中起到重要作用。在Go语言&#xff08;Golang&#xff09;中&#xff0c;关…

虚拟拍摄,如何用stable diffusion制作自己的形象照?

最近收到了某活动的嘉宾邀请&#xff0c;我将分享&#xff1a; 主题&#xff1a;生成式人工智能的创新实践 简要描述&#xff1a;从品牌营销、智能体、数字内容创作、下一代社区范式等方面&#xff0c;分享LLM与图像等生成式模型的落地应用与实践经验。 领域/研究方向&#xff…