Java JMH难题:StringBuilder vs StringBand
我很难理解这个基准是怎么回事。我想比较一下我的示例类Java JMH难题:StringBuilder vs StringBand,java,benchmarking,jmh,Java,Benchmarking,Jmh,我很难理解这个基准是怎么回事。我想比较一下我的示例类StringBand与StringBuilder的工作方式。StringBand的思想是在toString()处连接字符串,而不是在append()上 来源 以下是StringBand源代码-为基准测试精简: public class StringBandSimple { private String[] array; private int index; private int length; public StringBandSimpl
StringBand
与StringBuilder
的工作方式。StringBand
的思想是在toString()
处连接字符串,而不是在append()
上
来源
以下是StringBand
源代码-为基准测试精简:
public class StringBandSimple {
private String[] array;
private int index;
private int length;
public StringBandSimple(int initialCapacity) {
array = new String[initialCapacity];
}
public StringBandSimple append(String s) {
if (s == null) {
s = StringPool.NULL;
}
if (index >= array.length) {
//expandCapacity();
}
array[index++] = s;
length += s.length();
return this;
}
public String toString() {
if (index == 0) {
return StringPool.EMPTY;
}
char[] destination = new char[length];
int start = 0;
for (int i = 0; i < index; i++) {
String s = array[i];
int len = s.length();
//char[] chars = UnsafeUtil.getChars(s);
//System.arraycopy(chars, 0, destination, start, len);
s.getChars(0, len, destination, start);
start += len;
}
return new String(destination);
}
}
分析
下面是我对添加两个20个字符的字符串的理解
字符串拼接
已创建(36个字符)新字符[20+16]
- 调用
将20个arraycopy
字符复制到string1
StringBuilder
- 在第二次追加之前,
会扩展容量,因为40>36StringBuilder
- 因此,将创建新的字符[36*2+2]
将20个字符复制到新缓冲区arraycopy
共20个字符,用于追加sencondarraycopy
string2
- 最后,
返回toString()
新字符串(缓冲区,0,40)
创建新字符串[2]
- 这两个追加都只是将字符串保留在内部缓冲区中,直到调用
toString()
增加两次长度
(结果字符串的总长度)创建新字符[40]
包含20个第一个字符串字符(arraycopy
提供字符串的实UnsafeUtil
缓冲区)char[]
20秒字符串字符arraycopy
- 最后,返回新字符串(缓冲区,0,40)
StringBand
我们有:
- 少一个
-这样做的全部目的是什么arraycopy
- 更少的分配大小:
和新字符串[]
与两个新字符[]
新字符[]
- 此外,我们没有像
方法那样进行太多的检查(针对大小等)StringBuilder
StringBand
的工作原理至少与StringBuilder
相同,如果不是更快的话
基准结果
我正在MacBookPro上运行基准测试,2013年年中。使用JMH v0.2和Java 1.7b45
命令:
java -jar build/libs/microbenchmarks.jar .*StringBand.* -wi 2 -i 10 -f 2 -t 2
预热迭代次数(2)很好,因为我可以看到第二次迭代达到相同的性能
Benchmark Mode Thr Count Sec Mean Mean error Units
j.b.s.StringBandBenchmark.stringBand2 thrpt 2 20 1 37806.993 174.637 ops/ms
j.b.s.StringBandBenchmark.stringBuilder2 thrpt 2 20 1 76507.744 582.131 ops/ms
结果表明,StringBuilder
的速度快了两倍。当我将线程数增加到16个,或者在代码中显式使用BlackHole
s时,也会发生同样的情况
为什么?首先,由于使用这种方法的对象开销,您在内存中存储了更多数据 性能问题的原因可能是这一部分
char[] chars = UnsafeUtil.getChars(s);
System.arraycopy(chars, 0, destination, start, len);
由于无法从字符串中获取char[]
,因此必须将其复制到内存中,然后将其复制回目的地
你可以试着用
s.getChars(0,len,destination,start)
这使您可以直接在字符串中访问char[]
,并将其传递给系统。arraycopy
好的,像往常一样,“猫头鹰不是它们看起来的样子”。通过检查Java代码来推断代码性能很快就会变得奇怪。通过查看字节码进行推理的感觉也是一样的。生成的代码反汇编应该能更清楚地说明这一点,即使在一些小情况下,程序集的级别太高,无法解释这种现象
这是因为平台在各个级别上都对代码进行了大量优化。以下是您应该查看的提示。在i5 2.0 GHz、Linux x86_64、JDK 7u40上运行基准测试
基线:
Benchmark Mode Thr Count Sec Mean Mean error Units
j.b.s.StringBandBenchmark.stringBand2 thrpt 2 20 1 25800.465 297.737 ops/ms
j.b.s.StringBandBenchmark.stringBuilder2 thrpt 2 20 1 55552.936 876.021 ops/ms
Benchmark Mode Thr Count Sec Mean Mean error Units
j.b.s.StringBandBenchmark.stringBand2 thrpt 2 20 1 25727.363 207.979 ops/ms
j.b.s.StringBandBenchmark.stringBuilder2 thrpt 2 20 1 17233.953 219.510 ops/ms
是的,令人惊讶。现在,看这个。我袖子里什么都没有,除了
-XX:-优化StringConcat:
Benchmark Mode Thr Count Sec Mean Mean error Units
j.b.s.StringBandBenchmark.stringBand2 thrpt 2 20 1 25800.465 297.737 ops/ms
j.b.s.StringBandBenchmark.stringBuilder2 thrpt 2 20 1 55552.936 876.021 ops/ms
Benchmark Mode Thr Count Sec Mean Mean error Units
j.b.s.StringBandBenchmark.stringBand2 thrpt 2 20 1 25727.363 207.979 ops/ms
j.b.s.StringBandBenchmark.stringBuilder2 thrpt 2 20 1 17233.953 219.510 ops/ms
禁止VM进行字符串优化会产生“预期”的结果,如原始分析中所述。众所周知,HotSpot对StringBuilder进行了优化,有效地识别了常见的习惯用法,如newStringBuilder().append(…).append(…).toString()
,并为语句生成更有效的代码
分解并弄清楚应用的字符串优化到底发生了什么,留给感兴趣的读者作为练习:)如果发布最小化的基准项目,第一次收听StringBand
会更容易。可能吗?在这里,请参阅更新的问题。非常感谢。可以获取char[]
,请参阅UnsafeUtil
的源代码。无论如何,你说得对,我可以使用getChar
,但仍然没有区别。UnsageUtil并不总是返回char[]字段。由于反射等原因,这需要一些过度的思考。你可以尝试的是运行你的工作台,而不使用to-string部分,再次使用to-string并更新结果。正如我所说,我这样做了,没有任何改变。仍然相差2倍UnsafeUtil
在传统意义上不使用反射。@Damianleszczcski Vash UnsafeUtil不使用反射(在任何意义上),并清楚地证明有一种方法可以将字符[]从字符串中取出…@NitsanWakart。访问不安全的实例是不安全的,但这是一件小事。太棒了!一旦我将代码重写为:StringBuilder sb=newstringbuilder(string1);某人追加(第2条);使某人返回字符串()代码>结果与预期一致。再次感谢您!