java – JMH puzzle:StringBuilder vs StringBand
It's hard for me to understand the progress of this benchmark I want to measure how my sample class stringband works compared to StringBuilder The idea of stringband is to concatenate strings on toString () instead of append ()
source
This is the stringband source – split benchmark:
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,destination,start,len); s.getChars(0,len,start); start += len; } return new String(destination); } }
This code uses: unsafeutil Getchars() actually gets string char [] without copying, see code here We can also use getchars (), which is still the same
This is the jmh test:
@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(); } }
analysis
This is my understanding of what happens when two strings of 20 characters are added
StringBuilder
>Create new characters [20 16] (36 characters) > call arraycopy to copy 20 string1 characters to StringBuilder > before the second attachment, StringBuilder expands the capacity because 40 > 36 > therefore, create a new char [36 * 2 2] > copy the array of 20 characters to the arraycopy of new buffer > 20 characters to attach sencond string2 > finally, Tostring() returns a new string (buffer, 40)
StringBand
>Create a new string [2] > both just keep the string in the internal buffer until tostring() > the length is increased twice > create a new char [40] (the total length of the result string) > arraycopy of 20 first string characters (unsafeutil provides the real char [] buffer of the character string) > arraycopy of 20 second string characters > finally, Returns a new string (buffer, 40)
expect
Using stringband, we have:
>One less arraycopy – what is the purpose of this > less allocated size: new string [] and new char [] and two new char [] > plus we don't have as many checks as the StringBuilder method (for size, etc.)
So I hope stringband is at least the same as StringBuilder, if not faster
Benchmark test results
I ran benchmarks on MacBook Pro in mid - 2013 Use JMH v0 2 and Java 1.7b45
Command:
java -jar build/libs/microbenchmarks.jar .*StringBand.* -wi 2 -i 10 -f 2 -t 2
The number of warm - up iterations (2) is good because I can see that the second iteration achieves the same performance
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
The results show that the speed of StringBuilder is doubled The same happens when I increase the number of threads to 16 or explicitly use Blackholes in my code
Why?
Solution
Well, as usual, "owls are not what they look like." It's strange to reason about code performance by checking java code quickly It feels the same to reason by looking at the bytecode The generated code disassembly should know more about this, even in some small cases, the assembly is too high to explain this phenomenon
This is because the platform has greatly optimized the code at all levels This is a hint you should read At i5 2.0 GHz, Linux x86_ 64, run your benchmark on JDK 7u40
Baseline:
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
Yes, it's amazing Now, look at this There's nothing in my sleeve except
-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
Disabling VM string optimization will produce "expected" results, as described in the original analysis As we all know, hotspot has the optimization function of stringbuilders and can effectively identify new stringbuilders () append(…). append(…). Tostring() and other common idioms, and generate more effective code for statements
Disassemble and find out what happened to the applied string optimization, and leave it to the interested readers to practice:)