I\'ve a performance related question regarding use of StringBuilder.
In a very long loop I\'m manipulating a StringBuilder
and passing it to another method like
The fastest way is to use "setLength". It won't involve the copying operation. The way to create a new StringBuilder should be completely out. The slow for the StringBuilder.delete(int start, int end) is because it will copy the array again for the resizing part.
System.arraycopy(value, start+len, value, start, count-end);
After that, the StringBuilder.delete() will update the StringBuilder.count to the new size. While the StringBuilder.setLength() just simplify update the StringBuilder.count to the new size.
The second one is about 25% faster in my mini-benchmark.
public class ScratchPad {
static String a;
public static void main( String[] args ) throws Exception {
long time = System.currentTimeMillis();
for( int i = 0; i < 10000000; i++ ) {
StringBuilder sb = new StringBuilder();
sb.append( "someString" );
sb.append( "someString2"+i );
sb.append( "someStrin4g"+i );
sb.append( "someStr5ing"+i );
sb.append( "someSt7ring"+i );
a = sb.toString();
}
System.out.println( System.currentTimeMillis()-time );
time = System.currentTimeMillis();
StringBuilder sb = new StringBuilder();
for( int i = 0; i < 10000000; i++ ) {
sb.delete( 0, sb.length() );
sb.append( "someString" );
sb.append( "someString2"+i );
sb.append( "someStrin4g"+i );
sb.append( "someStr5ing"+i );
sb.append( "someSt7ring"+i );
a = sb.toString();
}
System.out.println( System.currentTimeMillis()-time );
}
}
Results:
25265
17969
Note that this is with JRE 1.6.0_07.
Based on Jon Skeet's ideas in the edit, here's version 2. Same results though.
public class ScratchPad {
static String a;
public static void main( String[] args ) throws Exception {
long time = System.currentTimeMillis();
StringBuilder sb = new StringBuilder();
for( int i = 0; i < 10000000; i++ ) {
sb.delete( 0, sb.length() );
sb.append( "someString" );
sb.append( "someString2" );
sb.append( "someStrin4g" );
sb.append( "someStr5ing" );
sb.append( "someSt7ring" );
a = sb.toString();
}
System.out.println( System.currentTimeMillis()-time );
time = System.currentTimeMillis();
for( int i = 0; i < 10000000; i++ ) {
StringBuilder sb2 = new StringBuilder();
sb2.append( "someString" );
sb2.append( "someString2" );
sb2.append( "someStrin4g" );
sb2.append( "someStr5ing" );
sb2.append( "someSt7ring" );
a = sb2.toString();
}
System.out.println( System.currentTimeMillis()-time );
}
}
Results:
5016
7516
Faster still:
public class ScratchPad {
private static String a;
public static void main( String[] args ) throws Exception {
final long time = System.currentTimeMillis();
// Pre-allocate enough space to store all appended strings.
// StringBuilder, ultimately, uses an array of characters.
final StringBuilder sb = new StringBuilder( 128 );
for( int i = 0; i < 10000000; i++ ) {
// Resetting the string is faster than creating a new object.
// Since this is a critical loop, every instruction counts.
sb.setLength( 0 );
sb.append( "someString" );
sb.append( "someString2" );
sb.append( "someStrin4g" );
sb.append( "someStr5ing" );
sb.append( "someSt7ring" );
setA( sb.toString() );
}
System.out.println( System.currentTimeMillis() - time );
}
private static void setA( final String aString ) {
a = aString;
}
}
In the philosophy of writing solid code, the inner workings of the method are hidden from the client objects. Thus it makes no difference from the system's perspective whether you re-declare the StringBuilder
within the loop or outside of the loop. Since declaring it outside of the loop is faster, and it does not make the code significantly more complicated, reuse the object.
Even if it was much more complicated, and you knew for certain that object instantiation was the bottleneck, comment it.
Three runs with this answer:
$ java ScratchPad
1567
$ java ScratchPad
1569
$ java ScratchPad
1570
Three runs with the other answer:
$ java ScratchPad2
1663
2231
$ java ScratchPad2
1656
2233
$ java ScratchPad2
1658
2242
Although not significant, setting the StringBuilder
's initial buffer size, to prevent memory re-allocations, will give a small performance gain.
I don't think that it make sence to try to optimize performance like that. Today (2019) the both statments are running about 11sec for 100.000.000 loops on my I5 Laptop:
String a;
StringBuilder sb = new StringBuilder();
long time = 0;
System.gc();
time = System.currentTimeMillis();
for (int i = 0; i < 100000000; i++) {
StringBuilder sb3 = new StringBuilder();
sb3.append("someString");
sb3.append("someString2");
sb3.append("someStrin4g");
sb3.append("someStr5ing");
sb3.append("someSt7ring");
a = sb3.toString();
}
System.out.println(System.currentTimeMillis() - time);
System.gc();
time = System.currentTimeMillis();
for (int i = 0; i < 100000000; i++) {
sb.setLength(0);
sb.delete(0, sb.length());
sb.append("someString");
sb.append("someString2");
sb.append("someStrin4g");
sb.append("someStr5ing");
sb.append("someSt7ring");
a = sb.toString();
}
System.out.println(System.currentTimeMillis() - time);
==> 11000 msec(declaration inside loop) and 8236 msec(declaration outside loop)
Even if I'am running programms for address dedublication with some billion loops a difference of 2 sec. for 100 million loops does not make any difference because that programs are running for hours. Also be aware that things are different if you only have one append statement:
System.gc();
time = System.currentTimeMillis();
for (int i = 0; i < 100000000; i++) {
StringBuilder sb3 = new StringBuilder();
sb3.append("someString");
a = sb3.toString();
}
System.out.println(System.currentTimeMillis() - time);
System.gc();
time = System.currentTimeMillis();
for (int i = 0; i < 100000000; i++) {
sb.setLength(0);
sb.delete(0, sb.length());
sb.append("someString");
a = sb.toString();
}
System.out.println(System.currentTimeMillis() - time);
==> 3416 msec(inside loop), 3555 msec(outside loop) The first statement which is creating the StringBuilder within the loop is faster in that case. And, if you change the order of execution it is much more faster:
System.gc();
time = System.currentTimeMillis();
for (int i = 0; i < 100000000; i++) {
sb.setLength(0);
sb.delete(0, sb.length());
sb.append("someString");
a = sb.toString();
}
System.out.println(System.currentTimeMillis() - time);
System.gc();
time = System.currentTimeMillis();
for (int i = 0; i < 100000000; i++) {
StringBuilder sb3 = new StringBuilder();
sb3.append("someString");
a = sb3.toString();
}
System.out.println(System.currentTimeMillis() - time);
==> 3638 msec(outside loop), 2908 msec(inside loop)
Regards, Ulrich
Declare once, and assign each time. It is a more pragmatic and reusable concept than an optimization.
In the philosophy of writing solid code its always better to put your StringBuilder inside your loop. This way it doesnt go outside the code its intended for.
Secondly the biggest improvment in StringBuilder comes from giving it an initial size to avoid it growing bigger while the loop runs
for (loop condition) {
StringBuilder sb = new StringBuilder(4096);
}