String replace is: 2ms
String is: 3300ms
StringBuffer is: 4ms
StringBuilder is: 6ms
Tan Quang wrote:Why is StringBuffer faster than StringBuilder in this case?
Tan Quang wrote:The next question, according to these two questions: Replace all occurrences of substring in a string - which is more efficient in Java? and String.replaceAll is considerably slower than doing the job yourself.
As can be seen, String.replaceAll() and String.replace() both use internal regex
Tan Quang wrote:and String.replace() will always generate a new string every time it is called.
Tan Quang wrote:And on top of that, all 2 deliver poor performance
Tan Quang wrote:although String.replace() is still rated as delivering better performance than String.replaceAll()
Tan Quang wrote:
StringBuffer is synchronous (slow).
StringBuilder is asynchronous (fast).
The problem with getting rid of the "undesirables" is that sooner or later someone will decide that YOU are an undesirable.
Tim Holloway wrote:Where the StringBuffer gets a lot slower is when multiple threads are all trying to use the same StringBuffer at the same time and perforce some threads must sleep while the current thread uses the resource.
Tim Holloway wrote:As to why StringBuffer and Vector were implemented with blocking abilities and StringBuilder and java List came later, that's a mystery to me. Perhaps they were used in that capacity somewhere in the core JVM.
Mike Simmons wrote:
That's pretty rare, though - why would anyone need to do that?
The problem with getting rid of the "undesirables" is that sooner or later someone will decide that YOU are an undesirable.
Mike Simmons wrote:I don't believe it is. First, your test case is far too short to measure a meaningful value. You should run the code many more times to get meaningful results. Second, when I run the code myself, I get faster results for StringBuilder, not StringBuffer. Maybe it depends on which JDK version you're using. If you're still using JDK 8 or whatever, it may be that there was some bug in the code that was fixed a long time ago - I don't know.
No. String.replace() doesn't use any regular expression. It treats the first argument as a literal. No regex involved. You may be confusing this method with other methods like replaceAll() and replaceFirst(). They did a bad job naming these methods, because they're not consistent. I always have to double-check the documentation to see which ones use regex and which don't.
So will replaceAll(). So will any String method, pretty much.
Compared to what? They don't do the same thing. Getting the optimal performance out of code with multiple replacements can be complicated - not every method is optimized for all situations.
Probably because it doen't use any regex. If you don't need regex, don't use it, it slows you down for simple cases. But if you need it (and it can be very useful), then use it.
You also have a test case at the beginning that is labeled as testing String replace(), but the code is using replaceAll(). It's also doing completely different things than your other test cases, so it doesn't meaningfully compare to anything else.
Tim Holloway wrote:No. StringBuffer is synchronized, not synchronous. That is, its services are managed by a lock so that it is thread-safe. StringBuilder is the same as StringBuffer except that its services do not implement the Java synchronized locking. Synchronous and asynchronous are better off used to describe threads than resources.
StringBuilder is faster than StringBuffer because the act of testing, acquiring and releasing a lock is extra logic that StringBuilder does not have. Less logic, faster time for the same overall algorithm, as a rule. As Mike said, your sampling size isn't large enough to show the difference accurately, though.
Where the StringBuffer gets a lot slower is when multiple threads are all trying to use the same StringBuffer at the same time and perforce some threads must sleep while the current thread uses the resource.
As to why StringBuffer and Vector were implemented with blocking abilities and StringBuilder and java List came later, that's a mystery to me. Perhaps they were used in that capacity somewhere in the core JVM.
Tim Driven Development | Test until the fear goes away
Because they work on the JVM development teams tuning the String classes at Oracle, IBM, OpenJPA, et. al.?Campbell Ritchie wrote:But why would anybody want to do micro‑benchmarking on such code in the first place?
But as for the rest of us, probably not.The problem with getting rid of the "undesirables" is that sooner or later someone will decide that YOU are an undesirable.
Stephan van Hulst wrote:As I already mentioned in one of your other threads, you may not draw conclusions from any benchmark written using currentTimeMillis(), nanoTime() or any timer classes.
If you didn't use a microbenchmark framework to get your timings, they are wrong.
Since you seem to be very preoccupied with micro-optimizations, at least use the proper tools to test them. I strongly recommend jmh.
Tim Cooke wrote:JMH seconded for microbenchmarking. Do not attempt to roll your own, ever.
Mike Simmons wrote:Re: replace() using replaceAll(), interesting. That code seems to be from Java 1.6 or 1.7, since it has methods added in 1.6 but not 1.8. I was looking at code from JDK 18 and JDK 11, and it does not use replaceAll() internally. I would guess that they found they could optimize it better that way. If you're still using Java 8 you should look at the source for Java 8. But this also points to another issue when measuring performance - it can vary from Java version to Java version, and also from OS to OS and computer to computer.
Tan Quang wrote:Although Java is version 11.0.4, but StringBuffer is faster than StringBuilder in the case of the example code I gave.
Tan Quang wrote:So, my conclusion on the first question: Concatenating strings using the "plus" operator, although easier to read, but yields the worst performance. Therefore, so it is necessary to avoid the use of string concatenation using the "plus" operator, and it is advisable to use StringBuffer or StringBuilder (on a case-by-case basis) for better performance?!
Mike Simmons wrote:
The problem is not concatenation with +, but the fact that you're creating a new immutable object (String) on each iteration of the loop, and (importantly) ]it's getting bigger each time. That's what makes this bad performance.
The problem with getting rid of the "undesirables" is that sooner or later someone will decide that YOU are an undesirable.
Tim Holloway wrote:I don't know if there's any particular badness about making larger and larger String instances as such. When you discard a String in favor of a new String, whether larger or smaller, it's not like there's going to be immediate storage reclamation. The garbage collection process has its own schedule.
But once the String gets to sizes like 10⁸ characters, GC will be necessary every few runs of the loopTim Holloway wrote:. . . I don't know if there's any particular badness about making larger and larger String instances as such. When you discard a String in favor of a new String, whether larger or smaller, it's not like there's going to be immediate storage reclamation. The garbage collection process has its own schedule. . . .

And isn't one of the best ways to mess up your performance to try to be too clever about optimisations?Optimization isn't don't what you "know" is efficient, it's doing what you've measured to be efficient. . . . .
Mike Simmons wrote:the key issue is that in the three code examples I shows, each String is being built by adding something to the old String.
The problem with getting rid of the "undesirables" is that sooner or later someone will decide that YOU are an undesirable.
Agree; it would actually be executed at compile time and a String of 100 stars created.Tim Holloway wrote: . . . The second case would almost certainly be optimised at compile time into a single assignment of a constructed String of 100 stars --- constant folding. . . . .
Tan Quang wrote:StringBuffer is faster than StringBuilder in my test code (perhaps or sure) is due to the difference between the Java versions?!
So, my conclusion on the first question: Concatenating strings using the "plus" operator, although easier to read, but yields the worst performance. Therefore, so it is necessary to avoid the use of string concatenation using the "plus" operator, and it is advisable to use StringBuffer or StringBuilder (on a case-by-case basis) for better performance?!
Anyway, using String.replaceAll() is not advisable, too expensive, and offers poor performance with simple string replacement cases like the current one.
establishBaseline avgt 5 102,926 ± 10,111 ns/op
useConcatenationOnce avgt 5 98,797 ± 9,018 ns/op
useStringBuilderOnce avgt 5 100,541 ± 5,908 ns/op
useStringBufferOnce avgt 5 105,161 ± 3,027 ns/op
estabishBaseline avgt 5 0,193 ± 0,069 ns/op
useConcatenationManyTimes avgt 5 2947,531 ± 111,243 ns/op
useStringBuilderManyTimes avgt 5 12,964 ± 1,157 ns/op
useStringBufferManyTimes avgt 5 27,201 ± 5,807 ns/op
Stephan van Hulst wrote:Who cares? Stop worrying. Start profiling. You're wasting so much time worrying about stuff that might be inconsequential. Also, as you've already seen, improvements may be made to methods that you previous considered "too expensive", and rules you've made for yourself may no longer hold.
Mike Simmons wrote:The example you gave is still far too short to give meaningful results. I just tested it with JDK 8 and 11 using 100000000 repetitions, and StringBuilder was the clear winner on my machine - though as I repeated it more and more, the results got closer and closer. I agree with everyone encouraging you to use jmh - your tests are close to meaningless, as it is.
Paul Clapham wrote:Part of the performance equation is to not write complicated code when simple code would be equivalent. Doing that makes it hard to see what needs to be optimized and what doesn't.
The problem with getting rid of the "undesirables" is that sooner or later someone will decide that YOU are an undesirable.
Paul Clapham wrote:Part of the performance equation is to not write complicated code when simple code would be equivalent. Doing that makes it hard to see what needs to be optimized and what doesn't.
The purpose of the code you posted is simply to concatenate two Strings, although it's hard to realize that. So there is no reason to consider anything except simple String concatenation. Here's my simplified version of what you posted:
Mike Simmons wrote:One nice thing about using replaceAll() is that it can give you the opportunity to replace everything you want in a single pass:
(Here I pretended there was some difference between the different TIP_ITEM_NOTICE_BY_[ x ]_FLASH_END constants. As was probably intended - though Paul correctly observes that they're all the same in the code shown.)
The point is, there's just one replaceAll(), not three consecutive ones. Is that faster? Maybe, maybe not - the overhead of using a Pattern may still be greater. But the more variables you want to replace, the more it makes sense to do them all in one pass. Is it easier to read? That may depend on taste. Again, I think it scales nicely as you add more variables to replace (if there are any).
Admittedly, this is the replaceAll() in Matcher, not one in String. It works similarly, but it's designed to work with a MatchResult on each replacement.
The problem with getting rid of the "undesirables" is that sooner or later someone will decide that YOU are an undesirable.
Tan Quang wrote:Oh that's right, I hadn't thought of the string array before, because it was a bit difficult to read and a bit difficult to determine the index (in the case of my old code).
![]()
Tim Holloway wrote:I did a quick peek at some of the class sources. In OpenJDK7, the String replaceAll() method actually sets up a Matcher and returns its replaceAll() results. In OpenJDK8, the Matcher's replaceAll() allocates a StringBuffer and replaces into it. Other JRE's may be doing things differently, so Your Mileage May Vary.
Yes, that's what I said. A StringBuffer, not a StringBuilder. Presumably to ensure that the match is atomic.
Paul Clapham wrote:If you're going to have large amounts of text like that then it may be better to use a Properties file. This is essentially a Map<String, String> which is backed by a text file, When you need to change the text or add new text entries then you can just modify the text file, a much simpler process than adding more code to your program.
Tim Holloway wrote:Pattern matching is a fascinating thing.
Typically, you have a source pattern that is compiled before use. You can do a one-shot compile-and-go and that's less coding, but since compiling is overhead, if you intend to do repeated matches, a one-time compile is better.
The actual match operation is done by running the match pattern as instructions to the matcher which is a finite-state machine. In other words, a specialized bytecode interpreter dedicated to running the matches. It's quite efficient.
I did a quick peek at some of the class sources. In OpenJDK7, the String replaceAll() method actually sets up a Matcher and returns its replaceAll() results. In OpenJDK8, the Matcher's replaceAll() allocates a StringBuffer and replaces into it. Other JRE's may be doing things differently, so Your Mileage May Vary.
Yes, that's what I said. A StringBuffer, not a StringBuilder. Presumably to ensure that the match is atomic.
Mike Simmons wrote:I don't think so - like most StringBuffers and StringBuilders, it's used as a local variable only, and so there's no way to access it from any other thread. Synchronization is wasted there. Only a minor waste, since the lock will never be contended, but still a waste. I suspect it's just a case where the person who wrote the method was used to using StringBuffer and just did what they were used to. It looks like by OpenJDK 11 someone corrected it to use StringBuilder.
| Consider Paul's rocket mass heater. |