为什么indexOf
明显快于contains
,如果后者仅仅是第一个的包装?
来自Java API的代码:
public boolean contains(CharSequence s) {
return indexOf(s.toString()) > -1;
}
此thread中选择的答案显示了一个简短的测试,显示了差异。
此thread中选择的答案表明附加方法调用的开销无关紧要。那么,为什么差异呢?
请阅读我的编辑:几乎每个人都说微基准是有缺陷的。奇怪的是,它恰好反映了我的用例。
实际上,我并不怀疑indexOf
首先比contains
(对于我的用例)更快,我只是想知道原因。
我的意图是永远不要写基准!我只是在寻找最有效的方法来测试一个字符串是否包含另一个字符串(对于我的应用程序而言,它与基准测试无关,而是“现实生活中的情况”)。
答案 0 :(得分:10)
contains
方法实现为:
public boolean contains(CharSequence s) {
return indexOf(s.toString()) > -1;
}
这意味着如果CharSequence s
不是java.lang.String
,它可能会更慢,因为调用s.toString()
将导致新字符串实例的分配和初始化。如果s
是一个字符串 - 则不应存在任何可衡量的差异。
PS:这里的测试存在缺陷:https://stackoverflow.com/a/18340277/2588800 Java最初在"解释"中执行。模式,这是非常慢的,当它检测到一段代码被执行很多次时,它将它编译为本机代码,以加快它(阅读有关JIT编译)。
正如您所见contains
内部调用indexOf
,这意味着indexOf
最终将被编译为本机。因此,当他测试indexOf
时(注意他在contains
之后测试它),它可能已经被编译为本机代码。这就是时差的原因。尝试颠倒测试的顺序 - 首先测试indexOf
然后contains
,我打赌你会看到相反的结果。
Benchmark Mode Cnt Score Error Units
StringSearchBenchmark.testContains thrpt 500 22,071 ± 0,269 ops/us
StringSearchBenchmark.testIndexOf thrpt 500 22,654 ± 0,233 ops/us
正如您所看到的,差异可以忽略不计,可能会被其他方法调用(indexOf()
+ toString()
)和系统上的负载所淹没。
<强>源代码:强>
@Fork(1)
@State(Scope.Benchmark)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
@Measurement(iterations = 500, time = 50, timeUnit = TimeUnit.MILLISECONDS)
@Warmup(iterations = 10)
@BenchmarkMode(Mode.Throughput)
public class StringSearchBenchmark {
private static final String STATE = "absdefghijklmnopqrstuvwxyzabsdefghijklmnopqrstuvwxyzabsdefghijklmnopqrstuvwxyzabsdefghijklmnopqrstuvwxyz";
private static final String SEARCH_TERM = "abcd";
@Benchmark
public void testContains(Blackhole sink) {
sink.consume(STATE.contains(SEARCH_TERM));
}
@Benchmark
public void testIndexOf(Blackhole sink) {
sink.consume(STATE.indexOf(SEARCH_TERM));
}
}
答案 1 :(得分:2)
正如其他人所说的那样,基准测试存在严重缺陷 - 对Java代码的性能测试不就是这样 - 你必须将其加热以确保所有类都有已经加载和解析,所有对象都已加载到内存中,并且任何编译到本机代码,例如通过HotSpot,已经完成。一个天真的基准,你只需要在主方法中运行一次代码就不会真正开始。更好的选择是使用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中的微基准是 hard - 表面下有很多东西可以优化你的运行代码,而一些快捷方式可能会导致极其不准确的基准测试。
答案 2 :(得分:0)
indexOf是Hotspot JVM中的内部方法示例。这意味着此方法根本不使用java.lang.String中的java代码。这种方法有特殊的原生版本。您可以在此处找到此类方法的列表:do_intrinsic(_indexOf