• Post Reply Bookmark Topic Watch Topic
  • New Topic

StringBuffer not outperforming "+"

 
Mike Bradburn
Greenhorn
Posts: 3
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Hi,
I am outputting a large number of strings concatenated with variables in a servlet. The original way in which I did this was to simply call the println() method of the ServletOutputStream object on every line and enclose the strings and variable concatenated with the "+" operator in the paranthesis. There are approximately 1200 calls to the println() method. Although it's already running somewhat fast, I thought I would try to make it faster by using a StringBuffer instead of concatenating all of the strings and variables. I read in several books and on several websites that this is faster since it avoids the creation of a temporary StringBuffer object. So instead, I declared a StringBuffer object before any of the output statements and set its initial size to 60,000 characters (enough to accomodate all of the output so it would not have to resize itself). I then went through all of the code and used the append() method of the StringBuffer class to append all the strings and variables to the buffer. At the very end, I call the println() method of the ServletOutputStream object passing the StringBuffer object calling its toString() method as a parameter. After that, I set the reference to the object to null so that it could be garbage collected. I then tried issuing seven requests to this servlet with the version that uses the StringBuffer and seven requests (with the exact same parameters) to the version that uses concatenation and I timed them by forwarding the requests on to the JRunStats servlet that comes with JRun 3.1 (the app server I am using).
Here are my results:
===============================
With the StringBuffer() (in ms.)
===============================
request 1: 701
request 2: 60
request 3: 90
request 4: 300
request 5: 361
request 6: 60
request 7: 301
===============================
Without the StringBuffer() (in ms.)
===============================
request 1: 561
request 2: 90
request 3: 70
request 4: 70
request 5: 1292
request 6: 81
request 7: 80
As you can see, the version that uses the StringBuffer() is not outperforming the version that uses concatenation. Out of the seven requests, the version without the StringBuffer is faster in four of them. I can't figure out why this is. Does anyone here know?
Thanks,
Mike
 
Blake Minghelli
Ranch Hand
Posts: 331
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Well, seeing the vast differences from one request to another, I'd say there are other things at play. I think you must look at averages, in which StringBuffer wins. Also, I believe your memory footprint should be smaller with the StringBuffer.
 
Thomas Paul
mister krabs
Ranch Hand
Posts: 13974
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
I would need to see some code:
I ran this as a test.

Results:
total time (String): 77891
total time (StringBuffer): 15
Note: I saw very little difference in performance between StringBuffer created with an initial size and StringBuffer created using defaults.
[ September 16, 2002: Message edited by: Thomas Paul ]
 
Mike Bradburn
Greenhorn
Posts: 3
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Thanks for your replies, Blake and Thomas. First of all, the requests were indentical in both cases. The servlet does access a database and that would account for occasional spikes in the time but not overall.
Thomas, I modified your code to try to approximate what I am doing in the servlet in both cases as follows:

it took 0 ms for the section the uses the String to execute and 10 ms for the section that uses the StringBuffer to execute. By the way, you must have a powerful machine if you can execute the first section of your sample code in less than 80 seconds. It took my computer almost 200 seconds (with a 1GHz processor, 512 MB RAM, and only one application running in the background).
[ September 16, 2002: Message edited by: Mike Bradburn ]
 
Thomas Paul
mister krabs
Ranch Hand
Posts: 13974
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
I have dual 1.8GHz Xeon processors with 2GB RAM. I am running a database server and a web Server.
I think all this shows that the difference between String and StringBuffer can be slight depending on the circumstances. String may perform better in some circumstance but the difference is virtually non-existent (10ms). But under certain circumstances, StringBuffer will vastly outperform String. So it is almost always worthwhile to use StringBuffer.
[ September 16, 2002: Message edited by: Thomas Paul ]
 
Jamie Robertson
Ranch Hand
Posts: 1879
MySQL Database Suse
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
If you look at your bytecode ( after compiler optimizations ), in each loop you are actually performing
13 ldc #4 <String "aaaaaaaaaaaaaaaaaaaaaaaabbbcccccccccc">
vs.
80 ldc #14 <String "aaaaaaaaaaaaaaaaaaaaaaaa">
82 invokevirtual #9 <Method java.lang.StringBuffer append(java.lang.String)>
85 ldc #15 <String "bbb">
87 invokevirtual #9 <Method java.lang.StringBuffer append(java.lang.String)>
90 ldc #16 <String "cccccccccc">
92 invokevirtual #9 <Method java.lang.StringBuffer append(java.lang.String)>
In the first situation using Strings, after compiler optimization you are creating one large String and printing it out. The second situation when using StringBuffer, you are creating 3 different String objects, then calling the StringBuffer.append() method in each case. I would say that the second case should be much slower.
In a real case scenario, without optimizations, the second case would be faster. Try the same code with 'new String("aaaa...")' instead of "aaaa..." .
This is my first look into the bytecode, so point out anything that I may have misinterpreted.
Jamie
 
Thomas Paul
mister krabs
Ranch Hand
Posts: 13974
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Good point, Jamie. Since the same String is used over and over again, the optimizer can eliminate the String construction. This would not reflect a real life situation.
I changed the test slightly to prevent the String construction from being optimized out:

They both run exactly the same now. If you use new String() then the String method takes twice as long. (31ms vs. 15ms)
[ September 16, 2002: Message edited by: Thomas Paul ]
 
Chris Smith
Ranch Hand
Posts: 42
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
You're all apparently missing the point of the StringBuffer/String performance issue. The code provided here does this in a loop:
s = "some string" + i + "some string" + ...;
That's fine. It may or may not compile to something faster than the equivalent StringBuffer code. What doesn't work well with Strings is constant appending to the same string in a loop.
Try changing that to:
s += "some string" + i + "some string" + ...;
 
Chris Smith
Ranch Hand
Posts: 42
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Incidentally, another point made by this thread is to not trust benchmarking that uses times too small for the system clock resolution. For goodness sake, increase the number of iterations! When you get "0 milliseconds" as a benchmarking time, that's a surefire indication that your results are no good.
 
hockleyd
Greenhorn
Posts: 1
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
One other thing coming into play in some liklihood is that recent JVMs will actually *use* StringBuffer in concetenation operations where it is deemed appropriate. Thus you may be using the exact same operations in both cases under the hood.
For reference, the Wiki at c2 has: http://c2.com/cgi/wiki?StringBuffer
 
Thomas Paul
mister krabs
Ranch Hand
Posts: 13974
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Originally posted by Chris Smith:
Try changing that to: s += "some string" + i + "some string" + ...;

Yep, you are correct. The new numbers are:
total time (String):2390
total time (StringBuffer): 16
[ September 28, 2002: Message edited by: Thomas Paul ]
 
Chris Smith
Ranch Hand
Posts: 42
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
"Hockleyd" wrote:
One other thing coming into play in some liklihood is that recent JVMs will actually *use* StringBuffer in concetenation operations where it is deemed appropriate. Thus you may be using the exact same operations in both cases under the hood.

Not exactly accurate. String concatenation of compile-time constants will always be performed at compile-time. String concatenation within a statement will always be converted to an equivalent use of StringBuffer. This is not a feature of new JVMs... it's required behavior of the Java compiler.
The problem occurs, though, when the concatenations are in separate statements, and especially when they occur in the body of a loop. In this case, it would be very difficult for a compiler to detect the ability to use a StringBuffer and substitute its use. In any case, I'm unaware of compilers that do so.
As for the VM doing so as you said, in JIT code, I find that even less likely.
Chris Smith
 
Ilja Preuss
author
Sheriff
Posts: 14112
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Originally posted by Chris Smith:
String concatenation within a statement will always be converted to an equivalent use of StringBuffer. This is not a feature of new JVMs... it's required behavior of the Java compiler.

No, it's optional behaviour of the Java compiler:
Java Language Specification 15.18.1.2 - Optimization of String Concatenation
"[...] To increase the performance of repeated string concatenation, a Java compiler may use the StringBuffer class or a similar technique to reduce the number of intermediate String objects that are created by evaluation of an expression."
 
Chris Smith
Ranch Hand
Posts: 42
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
You're right... use of StringBuffer is not required by the compiler; it is, however, very sensible... the only other option being for the compiler to autogenerate code using toCharArray and System.arraycopy to perform the concatenation.
The JLS section you mention refers to avoiding creation of intermediate String objects by doing the concatenation using a single StringBuffer. AFAIK, that didn't come up.
You seem to have missed the whole point of the response though, which is that it's extremely unlikely for any compiler or JIT to replace repeated String concatenation in a loop with an equivalent use of StringBuffer.
 
Max Habibi
town drunk
( and author)
Sheriff
Posts: 4118
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Another factor is all of this is that a StringBuffer is synchronized. A String is not(of course not, why would it be?). My own experience is that for less then 300 or so operations with my current system, String + actually outperforms StringBuffers.
There are two other factors to consider here.
1) While a String + might be faster under some conditions, it will require more work on the part of the garbage collector: so you need to balance that.
2) I wonder if the people performing sample tests 'warmed up' their jvms by running the test program about a dozen times or so before actually taking measurements?
M, author
The Sun Certified Java Developer Exam with J2SE 1.4
[ October 06, 2002: Message edited by: Max Habibi ]
 
Ilja Preuss
author
Sheriff
Posts: 14112
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Originally posted by Chris Smith:
You seem to have missed the whole point of the response though, which is that it's extremely unlikely for any compiler or JIT to replace repeated String concatenation in a loop with an equivalent use of StringBuffer.

Didn't miss it, just didn't have anything usefull to add to it. I didn't mean to controvert your post - I aggree to it -, I just wanted to correct this minor mistake. Sorry for not making that clear enough...
 
Mike Brock
Greenhorn
Posts: 15
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
There is no difference between using the string concatenation operator and StringBuffer in a contiguous operation. Consider the two following pieces of code:
Sample 1:
========
System.out.println("Foo" + " " + "Bar");
========
Sample 2:
========
System.out.println(new StringBuffer("Foo").append(" ").append("Bar).toString());
========
These two pieces of code are 100% equivelent. Using javap (the bytecode profiler included with Sun's SDK can easily confirm this if you want). Whenever you use the concatenation operator, the compiler creates a local StringBuffer object. So the code in Sample 1 is actually re-arranged to the equivelent of Sample 2 at compile time.
So why use StringBuffer? Well, look at the following examples:
Sample 3:
========
String tmp;
for (int i = 0; i < 50; i++) {
tmp += i + " ";
}
========
Sample 4:
========
StringBuffer tmp;
for (int i = 0; i < 50; i++) {
tmp.append(i).append(" ");
}
========
In this case, the outcome is very different. Because in Sample 3, on each iteration of the loop, a new StringBuffer object will be created and then 'tmp' will be re-referenced to that new object. This is very expensive. Where-as Sample 4, much more prudently appends the data to an existing StringBuffer.
So whether or not using a StringBuffer or not makes a difference, clearly depends on the application.
Regards,
Mike.
 
  • Post Reply Bookmark Topic Watch Topic
  • New Topic
Boost this thread!