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个字符的字符串的理解

字符串拼接
  • 新字符[20+16]
    已创建(36个字符)
  • 调用
    arraycopy
    将20个
    string1
    字符复制到
    StringBuilder
  • 在第二次追加之前,
    StringBuilder
    会扩展容量,因为40>36
  • 因此,将创建新的字符[36*2+2]
  • arraycopy
    将20个字符复制到新缓冲区
  • arraycopy
    共20个字符,用于追加sencond
    string2
  • 最后,
    toString()
    返回
    新字符串(缓冲区,0,40)
弦带
  • 创建新字符串[2]
  • 这两个追加都只是将字符串保留在内部缓冲区中,直到调用
    toString()
  • 长度
    增加两次
  • 创建新字符[40]
    (结果字符串的总长度)
  • arraycopy
    包含20个第一个字符串字符(
    UnsafeUtil
    提供字符串的实
    char[]
    缓冲区)
  • arraycopy
    20秒字符串字符
  • 最后,返回新字符串(缓冲区,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条);使某人返回字符串()结果与预期一致。再次感谢您!