Reputation: 3802
It is well known that String.format() performance is terrible. I see big possible improvements in my (and probably very common) typical case. I print same structure of data many times. Let imagine the structure like "x:%d y:%d z:%d". I expect that the main trouble with String.format() is that it has to always parse formatting string. My question is: Is there some ready made class which would allow to read formatting string only once and then allow to quickly give string when variable parameters filled?? Usage shall look like this:
PreString ps = new PreString("x:%d y:%d z:%d");
String s;
for(int i=0;i<1000;i++){
s = ps.format(i,i,i);
}
I know it is possible - following is my quick & dirty example which do what I'm talking about and is about ~10 times faster at my machine:
public interface myPrintable{
boolean isConst();
String prn(Object o);
String prn();
}
public class MyPrnStr implements myPrintable{
String s;
public MyPrnStr(String s){this.s =s;}
@Override public boolean isConst() { return true; }
@Override public String prn(Object o) { return s; }
@Override public String prn() { return s; }
}
public class MyPrnInt implements myPrintable{
public MyPrnInt(){}
@Override public boolean isConst() { return false; }
@Override public String prn(Object o) { return String.valueOf((Integer)o); }
@Override public String prn() { return "NumMissing"; }
}
public class FastFormat{
myPrintable[] obj = new myPrintable[100];
int objIdx = 0;
StringBuilder sb = new StringBuilder();
public FastFormat() {}
public void addObject(myPrintable o) { obj[objIdx++] = o; }
public String format(Object... par) {
sb.setLength(0);
int parIdx = 0;
for (int i = 0; i < objIdx; i++) {
if(obj[i].isConst()) sb.append(obj[i].prn());
else sb.append(obj[i].prn(par[parIdx++]));
}
return sb.toString();
}
}
It is used like this:
FastFormat ff = new FastFormat();
ff.addObject(new MyPrnStr("x:"));
ff.addObject(new MyPrnInt());
ff.addObject(new MyPrnStr(" y:"));
ff.addObject(new MyPrnInt());
ff.addObject(new MyPrnStr(" z:"));
ff.addObject(new MyPrnInt());
for (int i = 0; i < rpt; i++) {
s = ff.format(i,i,i);
}
when I compare with
long beg = System.nanoTime();
for (int i = 0; i < rpt; i++) {
s = String.format("x:%d y:%d z:%d", i, i, i);
}
long diff = System.nanoTime() - beg;
For 1e6 iteration pre-formatting improves result by factor of ~10:
time [ns]: String.format() (+90,73%) 3 458 270 585
time [ns]: FastFormat.format() (+09,27%) 353 431 686
[EDIT]
As Steve Chaloner replied there is a MessageFormat which is quite doing what I want. So I tried the code:
MessageFormat mf = new MessageFormat("x:{0,number,integer} y:{0,number,integer} z:{0,number,integer}");
Object[] uo = new Object[3];
for (int i = 0; i < rpt; i++) {
uo[0]=uo[1]=uo[2] = i;
s = mf.format(uo);
}
And it is faster only by factor of 2. Not the factor of 10 which I hoped. Again see measurement for 1M iteration (JRE 1.8.0_25-b18 32bit):
time [s]: String.format() (+63,18%) 3.359 146 913
time [s]: FastFormat.format() (+05,99%) 0.318 569 218
time [s]: MessageFormat (+30,83%) 1.639 255 061
[EDIT2]
As Slanec replied there is org.slf4j.helpers.MessageFormatter. (I tried library version slf4j-1.7.12
)
I did tried to compare code:
Object[] uo2 = new Object[3];
beg = System.nanoTime();
for(long i=rpt;i>0;i--){
uo2[0]=uo2[1]=uo2[2] = i;
s = MessageFormatter.arrayFormat("x: {} y: {} z: {}",uo2).getMessage();
}
with code for MessageFormat given above in section [EDIT]. I did get following results for looping it 1M times:
Time MessageFormatter [s]: 1.099 880 912
Time MessageFormat [s]: 2.631 521 135
speed up : 2.393 times
So MessageFormatter is best answer so far yet my simple example is still little bit faster... So any ready made faster library proposal?
Upvotes: 23
Views: 3664
Reputation: 38444
I said I would deliver, and here it is. My pre-compilation-capable string formatting (working proof-of-concept) library: https://gitlab.com/janecekpetr/string-format
Using
StringFormat.format("x:{} y:{} z:{}", i, i, i)
I get very similar numbers to slf4j and log4j2.
However, when using
CompiledStringFormat format = StringFormat.compile("x:{} y:{} z:{}");
// and then, in the loop
format.format(i, i, i)
I get roughly 1/3 better numbers than your FastFormat
. Note that at this point, you must be formatting A LOT of strings to get significant differences.
Upvotes: 1
Reputation: 38444
If you're looking for a fast implementation, you need to look outside the JDK. You probably use slf4j for logging anyway, so let's look at its MessageFormatter
:
MessageFormatter.arrayFormat("x:{} y:{} z:{}", new Object[] {i, i, i}).getMessage();
On my machine (and a crude and flawed microbenchmark), it's around 1/6 slower than your FastFormat
class, and around 5-10 times faster than either String::format
or MessageFormat
.
Upvotes: 2
Reputation: 8202
It sounds like you want MessageFormat
From the documentation:
The following example creates a MessageFormat instance that can be used repeatedly:
int fileCount = 1273;
String diskName = "MyDisk";
Object[] testArgs = {new Long(fileCount), diskName};
MessageFormat form = new MessageFormat(
"The disk \"{1}\" contains {0} file(s).");
System.out.println(form.format(testArgs));
Upvotes: 9