我正在学习如何用JMH对事物进行微基准测试。我从看似简单的东西开始:StringBuilder
vsString +=
的字符串连接。
根据我的理解,我应该创建一个包含StringBuilder
实例的State
对象,因为我不想对其构造函数进行基准测试(无论如何我也不想每次迭代都为空(。String +=
测试也是如此 - 我希望State
中的String
对象与新字符串连接。
这是我的代码:
@State(Scope.Thread)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public class Test {
@State(Scope.Thread)
public static class BenchmarkState {
public StringBuilder builder;
public String regularString;
@Setup(Level.Iteration)
public void setup() {
builder = new StringBuilder();
regularString = "";
}
}
@Benchmark
public String stringTest(BenchmarkState state) {
state.regularString += "hello";
return state.regularString;
}
@Benchmark
public String stringBuilderTest(BenchmarkState state) {
state.builder.append("hello");
return state.builder.toString();
}
public static void main(String[] args) throws RunnerException {
Options opt = new OptionsBuilder()
.include(Test.class.getSimpleName())
.forks(1)
.timeUnit(TimeUnit.MILLISECONDS)
.mode(Mode.Throughput)
.measurementTime(TimeValue.seconds(10))
.build();
new Runner(opt).run();
}
}
它有效,但我在想 - 我不想在每次迭代结束时调用.toString()
。我只在测试串联。所以我决定通过返回null
来删除它。
但是,这发生在第一次预热迭代中:
java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOf(Arrays.java:3332)
at java.lang.AbstractStringBuilder.ensureCapacityInternal(AbstractStringBuilder.java:124)
at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:448)
at java.lang.StringBuilder.append(StringBuilder.java:136)
我知道如果 JMH 尽可能快地追加到StringBuilder
,我会很快耗尽内存,所以我对OutOfMemoryError
问题并不感到惊讶。但我不明白为什么builder.toString()
修复它。
所以我的问题是:
为什么
builder.toString()
避免OutOfMemoryError
问题?无论如何,StringBuilder
不是仍然将所有字符保留在内存中吗?假设我不希望
StringBuilder
的构造函数及其.toString()
方法成为基准测试的一部分,我如何正确编写此测试?
调用toString()
需要时间,并生成垃圾,需要 GC 运行,进一步减慢代码速度。
由于测试有时间限制,因此这些速度减慢可能会导致测试在消耗所有内存之前停止。如果您增加时间限制,即使使用toString
,代码也可能在 OOM 中失败,这只需要更长的时间。