正如其他人所说,这个基准测试非常有缺陷 - 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
在这里,我们使用contains获得了相同的结果,但是indexOf已经变慢以匹配contains。这是非常有趣的结果,为什么会发生这种情况呢?
可能是由于HotSpot认识到indexOf调用的结果从未被检查,而且由于它正在使用final类(String),因此HotSpot可能能够保证调用没有副作用。所以如果我们不关注结果并且调用没有副作用,那我们为什么还要调用它呢?HotSpot能够意识到方法调用是无意义的,并将其完全删除,这可能就是这里发生的事情。这肯定可以解释数量级差异。
但是,为什么对于contains却不能起作用呢?我只能假设这是因为contains接受CharSequence而不是String,CharSequence是一个抽象类,这恰好足以防止HotSpot优化方法调用。
这也表明在Java中进行微基准测试是困难的 - 在优化运行代码的表面下,有很多东西需要处理,一些捷径可能导致非常不准确的基准测试。
contains()
方法看起来比实际上慢。 - Kayaman