正如其他人所说,基准测试存在严重缺陷 - Java 代码的性能测试不是那样工作 - 你必须预热它以确保所有类都已加载和解析,所有对象都已加载到内存中,并且任何编译为本机代码,例如通过 HotSpot,已经完成。只在 main 方法中运行一次代码的幼稚基准测试并不会真正奏效。一个更好的选择是使用类似JMH 的东西。给定以下测试:
package com.stackoverflow.example;
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;
@OutputTimeUnit(TimeUnit.MICROSECONDS)
@Measurement(time = 250, timeUnit = TimeUnit.MILLISECONDS)
public class MyBenchmark {
private static final String[] names = new String[]{"jack", "jackson", "jason", "jadifu"};
public static void main(String[] args) throws RunnerException {
Options opt = new OptionsBuilder()
.include(MyBenchmark.class.getSimpleName())
.forks(1)
.build();
new Runner(opt).run();
}
@Benchmark
public void contains() {
names[0].contains("ja");
}
@Benchmark
public void containsExplicit() {
names[0].indexOf("ja".toString());
}
@Benchmark
public void indexOf() {
names[0].indexOf("ja");
}
@Benchmark
public void matches() {
names[0].matches(".*ja.*");
}
}
我得到以下结果:
Benchmark Mode Cnt Score Error Units
MyBenchmark.contains thrpt 20 219.770 ± 2.032 ops/us
MyBenchmark.containsExplicit thrpt 20 1820.024 ± 20.583 ops/us
MyBenchmark.indexOf thrpt 20 1828.234 ± 18.744 ops/us
MyBenchmark.matches thrpt 20 3.933 ± 0.052 ops/us
现在,这很有趣,因为它仍然表明contains 比indexOf 慢得多。但是,如果我将测试稍微更改为以下内容:
@Benchmark
public void contains() {
assert names[0].contains("ja");
}
@Benchmark
public void containsExplicit() {
assert names[0].indexOf("ja".toString()) == 0;
}
@Benchmark
public void indexOf() {
assert names[0].indexOf("ja") == 0;
}
@Benchmark
public void matches() {
assert names[0].matches(".*ja.*");
}
我得到以下结果:
Benchmark Mode Cnt Score Error Units
MyBenchmark.contains thrpt 20 220.480 ± 1.266 ops/us
MyBenchmark.containsExplicit thrpt 20 219.962 ± 2.329 ops/us
MyBenchmark.indexOf thrpt 20 219.706 ± 2.401 ops/us
MyBenchmark.matches thrpt 20 3.766 ± 0.026 ops/us
在此,我们得到了相同的包含结果,但 indexOf 已减速以匹配 contains。这是一个非常有趣的结果。为什么会这样?
可能是由于 HotSpot 认识到 indexOf 调用的结果从未被检查过,并且由于它采用 final 类 (String),因此 HotSpot 可能能够保证不会对称呼。因此,如果我们不查看结果并且调用没有副作用,我们为什么要这样做? HotSpot 能够意识到一个方法调用是没有意义的,并完全删除它,这可能就是这里发生的事情。它肯定会解释数量级的差异。
为什么这不适用于contains?我只能假设这是因为contains 接受CharSequence,而不是String,这是一个抽象类,这足以阻止HotSpot 优化方法调用。
这也表明微基准测试在 Java 中是困难 - 在优化运行代码方面有很多事情要做,一些捷径可能会导致基准测试极其不准确。 p>