为什么.toString()似乎为StringBuilder修复了OutOfMemoryError异常? [英] Why does .toString() seem to fix an OutOfMemoryError exception for StringBuilder?
问题描述
我正在学习如何使用JMH对事物进行微基准测试.我从一个看似简单的东西开始:StringBuilder
vs String +=
的字符串连接.
I am learning how to microbenchmark things with JMH. I started with something seemingly simple: string concatenation for StringBuilder
vs String +=
.
根据我的理解,我应该制作一个包含StringBuilder
实例的State
对象,因为我不想基准化其构造函数(也不想每次迭代都为空). String +=
测试也是如此-我希望将State
中的String
对象与新字符串连接起来.
From my understanding, I should make a State
object that contains an instance of StringBuilder
because I don't want to benchmark its constructor (nor do I want to an empty one every iteration anyway). Same goes for the String +=
test - I want a String
object in my State
to be concatenated with new strings.
这是我的代码:
@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
来删除它.
It works, but I was thinking - I don't want to call .toString()
at the end of every iteration. I am testing concatenation only. So I decided to remove it by just returning null
instead.
但是,这是在第一次热身迭代期间发生的:
But then, this happens during the first warmup iteration:
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()
会解决它.
I understand that I would run out of memory pretty quickly if JMH is appending to StringBuilder
as fast as it can, so I'm not surprised by the OutOfMemoryError
issue. But I don't understand why does builder.toString()
fix it.
所以我的问题是:
-
为什么
builder.toString()
可以避免OutOfMemoryError
问题?StringBuilder
仍然不把所有字符都保留在内存中吗?
Why does
builder.toString()
avoid anOutOfMemoryError
issue? Doesn'tStringBuilder
still keep all the characters in memory regardless?
假设我既不希望StringBuilder
的构造函数也不希望其.toString()
方法成为基准测试的一部分,那么我该如何正确编写此测试?
Assuming that I do NOT want neither StringBuilder
's constructor nor its .toString()
method to be part of the benchmark, how do I properly write this test?
推荐答案
调用toString()
需要花费时间,并产生垃圾,需要运行GC,从而进一步降低了代码的速度.
Calling toString()
takes time, and generates garbage, requiring GC runs, further slowing down the code.
由于测试有时间限制,因此这些减速可能会导致测试在消耗所有内存之前停止.如果增加时间限制,即使使用toString
,代码也可能因OOM而失败,只会花费很多时间.
Since testing has a time limit, those slowdowns likely cause test to stop before it consumes all memory. If you increase the time limit, the code will likely fail with OOM even with the toString
, it will just take a LOT longer.
这篇关于为什么.toString()似乎为StringBuilder修复了OutOfMemoryError异常?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!