我很难理解这个基准测试的内容。我想测量我的示例类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);
}
}
此代码使用:UnsafeUtil.getChars()
实际获取String
char []而不进行复制,请参阅代码here。我们也可以使用getChars()
,它仍然是相同的。
这是JMH测试:
@State
public class StringBandBenchmark {
String string1;
String string2;
@Setup
public void prepare() {
int len = 20;
string1 = RandomStringUtil.randomAlphaNumeric(len);
string2 = RandomStringUtil.randomAlphaNumeric(len);
}
@GenerateMicroBenchmark
public String stringBuilder2() {
return new StringBuilder(string1).append(string2).toString();
}
@GenerateMicroBenchmark
public String stringBand2() {
return new StringBandSimple(2).append(string1).append(string2).toString();
}
}
以下是我对添加两个字符串20个字符时发生的事情的理解。
new char[20+16]
(36个字符)arraycopy
被调用以将20 string1
个字符复制到StringBuilder
StringBuilder
扩展了容量,因为40&gt; 36 new char[36*2+2]
已创建arraycopy
20个字符到新缓冲区arraycopy
附加了string2
toString()
返回new String(buffer, 0, 40)
new String[2]
已创建toString()
length
增加两次new char[40]
(结果字符串的总长度)arraycopy
(UnsafeUtil
提供字符串的真实char[]
缓冲区)arraycopy
20秒字符串字符new String(buffer, 0, 40)
我们拥有StringBand
:
arraycopy
- 这样做的目的是什么new String[]
和new char[]
与两个new char[]
StringBuilder
方法(大小等)所以我希望StringBand
至少与StringBuilder
相同,如果不是更快的话。
我在2013年中期在MacBookPro上运行基准测试。使用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
时,也会发生同样的情况。
为什么吗
答案 0 :(得分:21)
好吧,像往常一样,“猫头鹰不是他们看起来的样子”。通过快速检查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
是的,令人惊讶。现在,看看这个。除了...之外,我的袖子里什么都没有。
<强> -XX:-OptimizeStringConcat:强>
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具有围绕StringBuilders的优化,有效地识别像new StringBuilder().append(...).append(...).toString()
这样的惯用语,并为语句生成更有效的代码。
拆解并弄清楚所应用的字符串优化究竟发生了什么,留给感兴趣的读者练习:)
答案 1 :(得分:0)
首先,由于使用这种方法的对象开销,您在内存中存储了更多数据。
性能问题的原因可能是这部分
char[] chars = UnsafeUtil.getChars(s);
System.arraycopy(chars, 0, destination, start, len);
由于无法从字符串中获取char[]
,您必须将其复制到内存中,然后将其复制回destination
。
您可以尝试用
替换它 s.getChars(0,len,destination,start)
这样,您就可以直接在char[]
访问String
并将其传递给System.arraycopy
。