本文最后更新于 1061 天前,其中的信息可能已经有所发展或是发生改变。
前言
我们写的代码性能如何?用StopWatch来监控方法得出方法执行时间就是准确的?
使用JMH就可以回答第一个问题。JMH是方法级别的性能测试工具,并且是openjdk官方开发的(值得信赖),它有很多针对性能测试的功能,例如预热,该功能就可以解决StopWatch测试不准确的问题。
StopWatch测试不准确的原因如下:
- Java8引入的lambda表达式具有首次初始化开销。
lambda表达式的类是运行时生成的,而不是从类路径加载的。然而,生成类并不是速度变慢的原因。毕竟,生成一个结构简单的类比从外部源加载相同的字节还要快。内部类也必须加载。但是,当应用程序以前没有使用lambda表达式时,必须加载用于生成lambda类的框架(Oracle当前的实现在幕后使用ASM)。这是导致十几个内部使用的类(而不是lambda表达式本身)减速、加载和初始化的真正原因。
-
JIT编译器的优化
HotSpot虚拟机采用的是解释执行和编译执行的混合执行方式,两者的性能是有区别的。JIT编译器可将字节码编译成本机机器代码,其运行速度会更快。但是编译是有成本的,虚拟机只会编译热点代码,这是可以通过jvm参数控制的。
准备工作
导入依赖
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-core</artifactId>
<version>${jmh.version}</version>
</dependency>
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-generator-annprocess</artifactId>
<version>${jmh.version}</version>
</dependency>
使用
测试String,StringBuilder,StringBuffer字符串拼接
@Fork(1)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@Warmup(iterations = 3, time = 100, timeUnit = TimeUnit.MILLISECONDS)
@Measurement(iterations = 2, time = 100, timeUnit = TimeUnit.MILLISECONDS)
public class JmhForString {
public static final int STRING_ELEMENT_COUNT = 10_000;
public static void main(String[] args) throws RunnerException {
final Options options = new OptionsBuilder().include(JmhForString.class.getName())
.build();
new Runner(options).run();
}
@Benchmark
public void testString(Blackhole blackhole) {
String str = "";
for (int i = 0; i < STRING_ELEMENT_COUNT; i++) {
str += i;
}
blackhole.consume(str);
}
@Benchmark
public void testStringBuilder(Blackhole blackhole) {
StringBuilder str = new StringBuilder("");
for (int i = 0; i < STRING_ELEMENT_COUNT; i++) {
str.append(i);
}
blackhole.consume(str);
}
@Benchmark
public void testStringBuffer(Blackhole blackhole) {
StringBuffer str = new StringBuffer("");
for (int i = 0; i < STRING_ELEMENT_COUNT; i++) {
str.append(i);
}
blackhole.consume(str);
}
}
输出结果
Benchmark Mode Cnt Score Error Units
JmhForString.testString avgt 2 251.133 ms/op
JmhForString.testStringBuffer avgt 2 0.373 ms/op
JmhForString.testStringBuilder avgt 2 0.193 ms/op
测试HashMap和CurrentHashMap的Get、Put
@Fork(1)
@BenchmarkMode(Mode.Throughput)
@OutputTimeUnit(TimeUnit.SECONDS)
@Warmup(iterations = 3, time = 100, timeUnit = TimeUnit.MILLISECONDS)
@Measurement(iterations = 1, time = 100, timeUnit = TimeUnit.MILLISECONDS)
@State(Scope.Benchmark)
public class JmhForHashMap {
public static final int TEST_COUNT = 100_000;
private HashMap<Object, Object> hashMapForGet;
private ConcurrentHashMap<Object, Object> concurrentHashMapForGet;
public static void main(String[] args) throws RunnerException {
final Options options = new OptionsBuilder().include(JmhForHashMap.class.getName())
.build();
new Runner(options).run();
}
@Setup
public void init() {
this.hashMapForGet = testHashMapPut();
this.concurrentHashMapForGet = testConcurrentMapPut();
}
@Benchmark
public HashMap<Object, Object> testHashMapPut() {
HashMap<Object, Object> hashMap = Maps.newHashMap();
IntStream.rangeClosed(1, TEST_COUNT)
.boxed()
.forEach(i -> hashMap.put(i / 2, i));
return hashMap;
}
@Benchmark
public ConcurrentHashMap<Object, Object> testConcurrentMapPut() {
ConcurrentHashMap<Object, Object> concurrentHashMap = new ConcurrentHashMap<>();
IntStream.rangeClosed(1, TEST_COUNT)
.boxed()
.forEach(i -> concurrentHashMap.put(i / 2, i));
return concurrentHashMap;
}
@Benchmark
public void testHashMapGet() {
IntStream.range(0, TEST_COUNT)
.boxed()
.forEach(i -> hashMapForGet.get(i));
}
@Benchmark
public void testConcurrentHashMapGet() {
IntStream.range(0, TEST_COUNT)
.boxed()
.forEach(i -> concurrentHashMapForGet.get(i));
}
}
输出结果
Benchmark Mode Cnt Score Error Units
JmhForHashMap.testConcurrentHashMapGet thrpt 764.034 ops/s
JmhForHashMap.testConcurrentMapPut thrpt 112.291 ops/s
JmhForHashMap.testHashMapGet thrpt 946.438 ops/s
JmhForHashMap.testHashMapPut thrpt 277.580 ops/s