JMH初体验

Java 8

JMH 1.19

Eclipse Version: 2021-03 (4.19.0)

---

 

JMH仓库

https://github.com/openjdk/jmh

 

https://mvnrepository.com/search?q=jmh

最新版本:1.33 但没用起来,出现了OOM错误(后文解决了此问题)

JMH初体验

JMH初体验

 

JMH is a Java harness for building, running, and analysing nano/micro/milli/macro benchmarks written in Java and other languages targetting the JVM.

JMH 是 OpenJDK 团队开发的一款基准测试工具,一般用于代码的性能调优,精度甚至可以达到纳秒级别,适用于 java 以及其他基于 JVM 的语言。

JMH是Java性能测试工具,主要是对工程中一些方法进行一些基准测试,支持的时间单位为:nano / micro / milli / macro

 

根据参考文档1,添加了下面的测试程序:

package jmh;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.TimeUnit;

import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Level;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.State;
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;

/**
 * 使用JMH测试ArrayList、LinkedList的性能——add
 * @author ben
 * @date 2021-09-23 20:31:23 CST
 */
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
@State(Scope.Thread)
public class Jmh1 {

	// java.lang.OutOfMemoryError: Java heap space
	// at java.util.Arrays.copyOf(Arrays.java:3181)
	public final static String DATA = "DUMMY DATA";
//	public final static String DATA = "D";
//	public final static String DATA = "";
	
	private List<String> arrayList;
	private List<String> linkedList;
	
	public static int cnt = 0;
	
	@Setup(Level.Iteration)
	public void setUp() {
		arrayList = new ArrayList<String>();
		linkedList = new LinkedList<String>();
	}
	
	@Benchmark
	public List<String> arrayListAdd() {
		cnt++;
		try {
			this.arrayList.add(DATA);
		} catch (OutOfMemoryError e) {
			// cnt=157704908, size=157704907
			System.out.println("cnt=" + cnt + ", size=" + this.arrayList.size());
			throw e;
		}
		return this.arrayList;
	}
	
	@Benchmark
	public List<String> linkedListAdd() {
		this.linkedList.add(DATA);
		return linkedList;
	}
	
	public static void main(String[] args) throws RunnerException {
		final Options opts = new OptionsBuilder()
				.include(Jmh1.class.getSimpleName())
				.forks(1)
				.measurementIterations(10)
				.warmupIterations(10)
				.build();
		new Runner(opts).run();
	}

}

因为是maven项目,pom.xml中添加了下面的依赖包(前面截图中展示了):来自博客园

<!-- JMH -->
	<!-- https://mvnrepository.com/artifact/org.openjdk.jmh/jmh-core -->
	<dependency>
	    <groupId>org.openjdk.jmh</groupId>
	    <artifactId>jmh-core</artifactId>
	    <version>1.19</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.19</version>
        <!-- provided, compile 都可以 -->
	    <scope>provided</scope>
	</dependency>

 

在Eclipse中启动,出现下面的错误:java.lang.RuntimeException: ERROR: Unable to find the resource: /META-INF/BenchmarkList

JMH初体验

根据 参考文档2 解决了问题:Eclipse安装插件 m2e-apt 1.5.3,并做配置

JMH初体验

JMH初体验

来自博客园

不过,安装插件并配置后,对其它项目的执行有什么影响吗?还需研究下,TODO

 

上面使用的 JMH版本是 1.19,运行main函数:此时,笔记本的风扇呼呼地响,CPU使用率不用说,直接100%!/心疼电脑

测试结果
# JMH version: 1.19
# VM version: JDK 1.8.0_202, VM 25.202-b08
# VM invoker: D:\Program Files\Java\jdk1.8.0_202\jre\bin\java.exe
# VM options: -Dfile.encoding=UTF-8
# Warmup: 10 iterations, 1 s each
# Measurement: 10 iterations, 1 s each
# Timeout: 10 min per iteration
# Threads: 1 thread, will synchronize iterations
# Benchmark mode: Average time, time/op
# Benchmark: jmh.Jmh1.arrayListAdd

# Run progress: 0.00% complete, ETA 00:00:40
# Fork: 1 of 1
# Warmup Iteration   1: 0.015 us/op
# Warmup Iteration   2: 0.024 us/op
# Warmup Iteration   3: 0.015 us/op
# Warmup Iteration   4: 0.016 us/op
# Warmup Iteration   5: 0.017 us/op
# Warmup Iteration   6: 0.012 us/op
# Warmup Iteration   7: 0.021 us/op
# Warmup Iteration   8: 0.010 us/op
# Warmup Iteration   9: 0.010 us/op
# Warmup Iteration  10: 0.012 us/op
Iteration   1: 0.011 us/op
Iteration   2: 0.020 us/op
Iteration   3: 0.016 us/op
Iteration   4: 0.027 us/op
Iteration   5: 0.020 us/op
Iteration   6: 0.013 us/op
Iteration   7: 0.023 us/op
Iteration   8: 0.017 us/op
Iteration   9: 0.011 us/op
Iteration  10: 0.020 us/op


Result "jmh.Jmh1.arrayListAdd":
  0.018 ±(99.9%) 0.008 us/op [Average]
  (min, avg, max) = (0.011, 0.018, 0.027), stdev = 0.005
  CI (99.9%): [0.010, 0.025] (assumes normal distribution)


# JMH version: 1.19
# VM version: JDK 1.8.0_202, VM 25.202-b08
# VM invoker: D:\Program Files\Java\jdk1.8.0_202\jre\bin\java.exe
# VM options: -Dfile.encoding=UTF-8
# Warmup: 10 iterations, 1 s each
# Measurement: 10 iterations, 1 s each
# Timeout: 10 min per iteration
# Threads: 1 thread, will synchronize iterations
# Benchmark mode: Average time, time/op
# Benchmark: jmh.Jmh1.linkedListAdd

# Run progress: 50.00% complete, ETA 00:00:23
# Fork: 1 of 1
# Warmup Iteration   1: 0.458 us/op
# Warmup Iteration   2: 0.513 us/op
# Warmup Iteration   3: 0.115 us/op
# Warmup Iteration   4: 0.268 us/op
# Warmup Iteration   5: 0.113 us/op
# Warmup Iteration   6: 0.275 us/op
# Warmup Iteration   7: 0.271 us/op
# Warmup Iteration   8: 0.279 us/op
# Warmup Iteration   9: 0.099 us/op
# Warmup Iteration  10: 0.279 us/op
Iteration   1: 0.097 us/op
Iteration   2: 0.269 us/op
Iteration   3: 0.095 us/op
Iteration   4: 0.249 us/op
Iteration   5: 0.095 us/op
Iteration   6: 0.258 us/op
Iteration   7: 0.093 us/op
Iteration   8: 0.256 us/op
Iteration   9: 0.092 us/op
Iteration  10: 0.260 us/op


Result "jmh.Jmh1.linkedListAdd":
  0.176 ±(99.9%) 0.131 us/op [Average]
  (min, avg, max) = (0.092, 0.176, 0.269), stdev = 0.087
  CI (99.9%): [0.046, 0.307] (assumes normal distribution)


# Run complete. Total time: 00:01:18

Benchmark           Mode  Cnt  Score   Error  Units
Jmh1.arrayListAdd   avgt   10  0.018 ± 0.008  us/op
Jmh1.linkedListAdd  avgt   10  0.176 ± 0.131  us/op

 

最后两行展示如下:说明 ArrayList 的add的性能更好。

# Run complete. Total time: 00:01:18

Benchmark           Mode  Cnt  Score   Error  Units
Jmh1.arrayListAdd   avgt   10  0.018 ± 0.008  us/op
Jmh1.linkedListAdd  avgt   10  0.176 ± 0.131  us/op

 

测试中、测试后的JVM进程对比:测试中多了 Jmh1、ForkedMain两个进程

JMH初体验

 

正常测试完毕,接下来,使用最新版的 JMH 1.33进行测试——修改pom.xml文件。

执行测试:出现异常 java.lang.OutOfMemoryError: Java heap space,发生在 this.arrayList.add(DATA) 时。

# Warmup Iteration   1: <failure>

java.lang.OutOfMemoryError: Java heap space
	at java.util.Arrays.copyOf(Arrays.java:3181)
	at java.util.ArrayList.grow(ArrayList.java:265)

注,上面是 没有捕获异常时的输出

下面是本文展示的 捕获异常时的输出:第一个warmup居然执行了一亿五千多万次add!然后就发生了一次。

JMH初体验

这种情况下,LinkedList的测试是正常的,但ArrayList是没有测试结果的。

 

最新版本1.33为什么会这样呢?怎么解决?TODO

 

1.19版本执行时,ArrayList执行了多少次呢?输出每次++后的cnt:才八万多!和1.33的亿级完全没法比啊!难怪1.33会OOM

JMH初体验

---210923 2209---

 

解决1.33的OOM问题:来自博客园

检查@Benchmark注解所在包下的注解,原来还有 @Warmup、@Measurement ,按照参考文档1的说法,这两个也是用来控制测试过程的,可以添加到类和方法上。

JMH初体验

在main方法中,使用 OptionsBuilder 来建立Options对象,发现其下有多个 warmup、measurement开头的方法,是否使用这些方法可以来避免OOM呢?可以的。

下面的调用解决了问题:增加了一个 warmupTime、measurementTime 的调用,限定每次 预热、测试 在1秒以内——这样就不会调用add超亿次了吧(我的电脑太好、太快了?)

	public static void main(String[] args) throws RunnerException {
		final Options opts = new OptionsBuilder()
				.include(Jmh1.class.getSimpleName())
				.forks(1)
				// 预热:使代码的执行经历过了类的早起优化、JVM运行期编译、JIT优化
				.warmupIterations(10)
				.warmupTime(TimeValue.seconds(1L))
				// 真正的度量操作
				.measurementIterations(10)
				.measurementTime(TimeValue.seconds(1L))
				.build();
		new Runner(opts).run();
	}

 

更改后的测试结果:使用JMH 1.33成功进行了测试。来自博客园

注,前两行 Warmup、Measurement 后面的 1 s each——正是我们配置的,1.19版本也是这个值,而1.33版本时,这个值是 10 s each——难怪会超亿!

注,也可以使用前面提到的两个注解解决问题。

...
# Warmup: 10 iterations, 1 s each
# Measurement: 10 iterations, 1 s each
# Timeout: 10 min per iteration
...
# Run complete. Total time: 00:01:19

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           Mode  Cnt  Score   Error  Units
Jmh1.arrayListAdd   avgt   10  0.017 ± 0.012  us/op
Jmh1.linkedListAdd  avgt   10  0.170 ± 0.126  us/op

1.33版本的日志:

...
# Warmup: 10 iterations, 10 s each
# Measurement: 10 iterations, 10 s each
# Timeout: 10 min per iteration
# Threads: 1 thread, will synchronize iterations
# Benchmark mode: Average time, time/op
# Benchmark: jmh.Jmh1.arrayListAdd

# Run progress: 0.00% complete, ETA 00:06:40
# Fork: 1 of 1
# Warmup Iteration   1: <failure>

java.lang.OutOfMemoryError: Java heap space
	at java.util.Arrays.copyOf(Arrays.java:3181)
	at java.util.ArrayList.grow(ArrayList.java:265)
...

---210924 0657---

》》》全文完《《《

 

参考文档

1、《Java高并发编程详解 深入理解并发核心库》

书,by 汪文君

2、Eclipse Benchmark 基准测试 报错:ERROR: Unable to find the resource: /META-INF/BenchmarkList

3、arrays中copyof_Java中:常见的几种内存溢出及解决方案,再遇到后就可以解决了...

4、 

上一篇:《英语写作》翻译01


下一篇:STM32CubeMx 定时器实现 微妙级延迟函数