Measuring performance of Java String.format() (or lack thereof)

aka “Why is String.format() SO slow?”


In the past I have found that the functionality in the JDK is usually well optimized, and performs reasonably well for the task, considering general nature and applicability of the functionality offered. So as general guideline I tend to trust that if the JDK has a method for doing something in a simple way it is perfectly fine for most use and likely performs well enough.

More Specifically…

The specific case that caught my eye — and that represents a wider class of similar use cases — is that of String concatenation for creating Strings like compound keys. For example:

String key = String.format("%s.%s", keyspace, tableName);
String key = keyspace + "." + tableName; // or
key = new StringBuilder().append(keyspace)

But Should String.format() Have Low Overhead?

One thing to note, however, is that while usage here is for the same task, underlying implementation of String.format() is essentially much, much more complicated — it is a quite powerful tool for all types of String formatting. We just happen use this power tool for a low-power use case.

So Let’s Ask Our Friend JMH To Have a Look

Similar to my earlier post ‘Measuring “String.indexOfAny(String)” performance’, I added a new JMH test class — StringConcatenation — on repository.
You can run the test cases with:

java  -jar target/microbenchmarks.jar StringConcatenation
m1_StringFormat           thrpt   15    61337.088 ±   654.370  ops/s
m2_StringBuilder thrpt 15 2683849.107 ± 22092.481 ops/s
m3_StringBuilderPrealloc thrpt 15 2654994.965 ± 36881.162 ops/s
m4_ManualConcatenation thrpt 15 2700825.252 ± 27906.924 ops/s

Test cases

Before considering the numbers, let’s first introduce the test cases:

  • m2_StringBuilder: equivalent concatenation using StringBuilder as shown earlier
  • m3_StringBuilderPrealloc: sames as m2 but calculates optimal initial size of StringBuilder to avoid need to re-allocate its buffers. This is an attempt to further optimize m2 case
  • m4_ManualConcatenation: use of + operator: String str = first+"."+second; — which should internally become same or similar to m2 case

So Let’s Unpack The Results

So, let’s have a look at numbers we saw earlier. Looks like cases m2, m3 and m4 are about equivalent: giving us about 2.5 million iterations per second. With 32 concatenations that would to about 80 million concatenations per second (as well as other overhead for things like Garbage Collection). Not bad.

But What Does String.format() Do? (deeper dig)

Ok so it looks like String.format() takes its time in this case. But can we figure out what might be happening under the hood?

// @Measurement(iterations = 5, time = 1)
@Measurement(iterations = 3600, time = 1)
java  -jar target/microbenchmarks.jar StringConcatenation.m1
~/bin/async-profiler -e cpu -d 30 -f ~/profile-string-format.txt 67640
ns  percent  samples  top
---------- ------- ------- ---
3100000000 10.97% 310 java.util.regex.Pattern$Start.match
2970000000 10.51% 297 java.util.regex.Pattern$GroupHead.match
2370000000 8.39% 237 java.util.Formatter.format
2110000000 7.47% 211 java.lang.AbstractStringBuilder.ensureCapacityInternal
1590000000 5.63% 159 jshort_disjoint_arraycopy
1420000000 5.02% 142 java.util.Formatter$FormatSpecifier.index
1340000000 4.74% 134 java.util.Formatter.parse
1270000000 4.49% 127 arrayof_jint_fill
1240000000 4.39% 124 java.util.regex.Pattern$BmpCharProperty.match
1100000000 3.89% 110 java.util.regex.Pattern.matcher
990000000 3.50% 99 java.util.Formatter$FormatSpecifier.width
980000000 3.47% 98 java.util.regex.Pattern$Branch.match

So Does This Matter?

As usual, whether this performance difference really matters to your case…. depends. :)

  1. Are used for Exception processing (which has inherent overhead to being with)
  2. Are otherwise not often called

Open Source developer, most known for Jackson data processor (nee “JSON library”), author of many, many other OSS libraries for Java, from ClassMate to Woodstox