我很难理解这个基准发生了什么。我想测量我的示例类StringBand
与StringBuilder
. with 的想法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[] 而无需复制,请参见此处的代码。我们也可以使用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 > 36 - 因此,
new char[36*2+2]
被创建 arraycopy
新缓冲区的 20 个字符arraycopy
追加 20 个字符string2
- 最后,
toString()
返回new String(buffer, 0, 40)
弦带
new String[2]
被建造- 两者都追加只是将字符串保留在内部缓冲区中,直到
toString()
被调用 length
增加了两次new char[40]
已创建(结果字符串的总长度)arraycopy
20个第一个字符串字符(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
在代码中显式使用 s 时,也会发生同样的情况。
为什么?