Should I Use Java's String.Format() If Performance Is Important

Should I use Java's String.format() if performance is important?

I wrote a small class to test which has the better performance of the two and + comes ahead of format. by a factor of 5 to 6.
Try it your self

import java.io.*;
import java.util.Date;

public class StringTest{

public static void main( String[] args ){
int i = 0;
long prev_time = System.currentTimeMillis();
long time;

for( i = 0; i< 100000; i++){
String s = "Blah" + i + "Blah";
}
time = System.currentTimeMillis() - prev_time;

System.out.println("Time after for loop " + time);

prev_time = System.currentTimeMillis();
for( i = 0; i<100000; i++){
String s = String.format("Blah %d Blah", i);
}
time = System.currentTimeMillis() - prev_time;
System.out.println("Time after for loop " + time);

}
}

Running the above for different N shows that both behave linearly, but String.format is 5-30 times slower.

The reason is that in the current implementation String.format first parses the input with regular expressions and then fills in the parameters. Concatenation with plus, on the other hand, gets optimized by javac (not by the JIT) and uses StringBuilder.append directly.

Runtime comparison

Performance between String.format and StringBuilder

After doing a little test with StringBuilder vs String.format I understood how much time it takes each of them to solve the concatenation. Here the snippet code and the results

Code:

String name = "stackover";
String lName = " flow";
String nick = " stackoverflow";
String email = "stackoverflow@email.com";
int phone = 123123123;

//for (int i = 0; i < 10; i++) {
long initialTime1 = System.currentTimeMillis();
String response = String.format(" - Contact {name=%s, lastName=%s, nickName=%s, email=%s, phone=%d}",
name, lName, nick, email, phone);
long finalTime1 = System.currentTimeMillis();
long totalTime1 = finalTime1 - initialTime1;
System.out.println(totalTime1 + response);

long initialTime2 = System.currentTimeMillis();
final StringBuilder sb = new StringBuilder(" - Contact {");
sb.append("name=").append(name)
.append(", lastName=").append(lName)
.append(", nickName=").append(nick)
.append(", email=").append(email)
.append(", phone=").append(phone)
.append('}');
String response2 = sb.toString();
long finalTime2 = System.currentTimeMillis();
long totalTime2 = finalTime2 - initialTime2;
System.out.println(totalTime2 + response2);
//}

After of run the code several times, I saw that String.format takes more time:

String.format: 46: Contact {name=stackover, lastName= flow, nickName= stackoverflow, email=stackoverflow@email.com, phone=123123123}
StringBuilder: 0: Contact {name=stackover, lastName= flow, nickName= stackoverflow, email=stackoverflow@email.com, phone=123123123}
String.format: 38: Contact {name=stackover, lastName= flow, nickName= stackoverflow, email=stackoverflow@email.com, phone=123123123}
StringBuilder: 0: Contact {name=stackover, lastName= flow, nickName= stackoverflow, email=stackoverflow@email.com, phone=123123123}
String.format: 51: Contact {name=stackover, lastName= flow, nickName= stackoverflow, email=stackoverflow@email.com, phone=123123123}
StringBuilder: 0: Contact {name=stackover, lastName= flow, nickName= stackoverflow, email=stackoverflow@email.com, phone=123123123}

But if I run the same code inside a loop, the result change.

String.format: 43: Contact {name=stackover, lastName= flow, nickName= stackoverflow, email=stackoverflow@email.com, phone=123123123}
StringBuilder: 0: Contact {name=stackover, lastName= flow, nickName= stackoverflow, email=stackoverflow@email.com, phone=123123123}
String.format: 1: Contact {name=stackover, lastName= flow, nickName= stackoverflow, email=stackoverflow@email.com, phone=123123123}
StringBuilder: 0: Contact {name=stackover, lastName= flow, nickName= stackoverflow, email=stackoverflow@email.com, phone=123123123}
String.format: 1: Contact {name=stackover, lastName= flow, nickName= stackoverflow, email=stackoverflow@email.com, phone=123123123}
StringBuilder: 0: Contact {name=stackover, lastName= flow, nickName= stackoverflow, email=stackoverflow@email.com, phone=123123123}

The first time String.format runs it takes more time, after of that the time is shorter even though it does not become constant as a result of StringBuilder

As @G.Fiedler said: "String.format has to parse the format string..."

With these results it can be said that StringBuilder is more efficient thanString.format

Performance: Java's String.format

I wrote a quick caliper benchmark to compare String.format() vs. StringBuilder, StringBuffer, normal String + operator, String.replace() and String.concat() methods:

public class StringFormatBenchmark extends SimpleBenchmark {

public void timeStringFormat(int reps) {
while (--reps >= 0) {
String s = String.format("test %d", reps);
}
}

public void timeStringBuilder(int reps) {
while (--reps >= 0) {
String s = new StringBuilder("test ").append(reps).toString();
}
}

public void timeStringBuffer(int reps) {
while (--reps >= 0) {
String s = new StringBuffer("test ").append(reps).toString();
}
}

public void timeStringPlusOperator(int reps) {
while (--reps >= 0) {
String s = "test " + reps;
}
}

public void timeReplace(int reps) {
while (--reps >= 0) {
String s = "test {}".replace("{}", String.valueOf(reps));
}
}

public void timeStringConcat(int reps) {
while (--reps >= 0) {
String s = "test ".concat(String.valueOf(reps));
}
}

public static void main(String[] args) {
new Runner().run(StringFormatBenchmark.class.getName());
}

}

The results follow (Java 1.6.0_26-b03, Ubuntu, 32 bits):

caliper2

Clearly String.format() is much slower (by an order of magnitude). Also StringBuffer is considerably slower than StringBuilder (as we were taught). Finally StringBuilder and String + operator are almost identical since they compile to very similar bytecode. String.concat() is a bit slower.

Also don't use String.replace() if simple concatenation is sufficient.

Does string.format is more efficient than append to string using '+'

Appending using operators is generally more efficient. Format has to take the string and find "%"'s and so, and replace them with corresponding values. Appending is simpler, and shorter to type!

Imagine you are the compiler.

Go through the string to find the %s symbol. Replace it with the value there. Then concatenate.

versus

Concatenate.

Is Java's printf() method efficient for concatenating and printing Strings?

Does Java's printf() use slow concatenation when creating Strings?

Definitely no. The concatenation is fine.

However, it's pretty slow because of the analysis of the format strings. There are quite a few possibilities and a lot of branching and what else.

It would be nearly(*) possible to parse the string upfront and use a List<FormatItem> where each FormatItem would take care of one argument (als also add all the preceding fixed text). Once I did something like this and it was 2+ times faster.

(*) It gets more complicated by things like %3$s.

Difference between formatted string and simple string

There's no difference in terms of the output it produces. The result variable will have the same contents in each case.

But String.format() is there so that you can do more powerful formatting, including specifying number of decimal places for floating point numbers, and the like.

I would expect the String.format() version to be relatively slow, simply because it's more powerful, and has to check the contents of the format string before performing essentially the same operation as you're doing in your second version. In other words, it has to spend some time working out what you want to do, whereas in the second version, the compiler can work it out for itself.

But, really, choosing one of your snippets over the other here for performance reasons looks like the kind of micro-optimisation that will almost never pay off. Unless you have a clear need for performance (i.e., this is running in a tight loop for something performance-critical), you should go for the one that's more readable.

Is it better practice to use String.format over string Concatenation in Java?

I'd suggest that it is better practice to use String.format(). The main reason is that String.format() can be more easily localised with text loaded from resource files whereas concatenation can't be localised without producing a new executable with different code for each language.

If you plan on your app being localisable you should also get into the habit of specifying argument positions for your format tokens as well:

"Hello %1$s the time is %2$t"

This can then be localised and have the name and time tokens swapped without requiring a recompile of the executable to account for the different ordering. With argument positions you can also re-use the same argument without passing it into the function twice:

String.format("Hello %1$s, your name is %1$s and the time is %2$t", name, time)

String.format() vs + operator

If you are looking for performance only I believe that using StringBuilder/StringBuffer is the most efficient way to build strings. Even if the Java compiler is smart enough to translate most of String concatenations to StringBuilder equivalent.

If you are looking for readability the String.format thing is the much clearer I think, and this is what I use also unless I need to rely on high performance.

So if your main concern is not performance, meaning this code is not in a path that is called a lot, you may prefer to use String.format as it gives a better idea of the resulting String (like you said).

Besides, using String.format lets you use the format thing, which means you can use it for padding Strings, formatting numbers, dates, and so on, which would make the code even worse if using simple concatenation.

Edit for Chuu:

Using JAD, you can see that the following code:

public class Test {
public static void main(String[] args) {
String str = "a" + "b" + "c";
String str2 = "foo" + str + "bar" + str;
System.out.println(str2);
}
}

when decompiled will look like:

public class Test {
public static void main(String[] args) {
String str = "abc";
String str2 = new StringBuilder("foo").append(str).append("bar").append(str).toString();
System.out.println(str2);
}
}

Proof of that can also be found using the javap utility that will show you the Java bytecode under a .class file:

public static void main(java.lang.String[] args);
0 ldc <String "abc"> [16]
2 astore_1 [str]
3 new java.lang.StringBuilder [18]
6 dup
7 ldc <String "foo"> [20]
9 invokespecial java.lang.StringBuilder(java.lang.String) [22]
12 aload_1 [str]
13 invokevirtual java.lang.StringBuilder.append(java.lang.String) : java.lang.StringBuilder [25]
16 ldc <String "bar"> [29]
18 invokevirtual java.lang.StringBuilder.append(java.lang.String) : java.lang.StringBuilder [25]
21 aload_1 [str]
22 invokevirtual java.lang.StringBuilder.append(java.lang.String) : java.lang.StringBuilder [25]
25 invokevirtual java.lang.StringBuilder.toString() : java.lang.String [31]
28 astore_2 [str2]
29 getstatic java.lang.System.out : java.io.PrintStream [35]
32 aload_2 [str2]
33 invokevirtual java.io.PrintStream.println(java.lang.String) : void [41]
36 return


Related Topics



Leave a reply



Submit