我可以在String.format中预编译格式字符串吗? (或者做任何其他事情来使格式化日志更快?)

时间:2015-04-20 12:29:22

标签: java string performance optimization string-formatting

众所周知that String.format() performance is terrible。我看到我的(可能很常见的)典型案例有很大的改进。我多次打印相同的数据结构。让我们想象一下像" x:%d y:%d z:%d"的结构。我希望String.format()的主要问题是它必须始终解析格式化字符串。我的问题是:是否有一些现成的类允许只读取一次格式化字符串然后允许在变量参数填充时快速给出字符串?用法应如下所示:

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); 
}

我知道这是可能的 - 以下是我的快速&amp;肮脏的例子,做我正在谈论的事情,在我的机器上快了约10倍:

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();
    }
}

它的用法如下:

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);
}

当我与

比较时
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;

对于1e6迭代,预格式化可以通过因子~10来改善结果:

time [ns]: String.format()     (+90,73%)  3 458 270 585 
time [ns]: FastFormat.format() (+09,27%)    353 431 686 

[编辑]

Steve Chaloner回复时,有一个MessageFormat正在做我想要的事情。所以我尝试了代码:

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);
}

只有2倍才更快。不是我希望的因素10。再次参见1M迭代的测量(JRE 1.8.0_25-b18 32位):

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]

Slanec回复时,有org.slf4j.helpers.MessageFormatter。 (我试过了库版slf4j-1.7.12

我确实试过比较代码:

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();
}

使用上面[EDIT]部分中给出的MessageFormat代码。我确实获得了以下结果,以便循环它1M次:

Time MessageFormatter [s]: 1.099 880 912
Time MessageFormat    [s]: 2.631 521 135
speed up : 2.393 times

所以到目前为止,MessageFormatter是最好的答案,但我的简单示例仍然快一点......那么任何现成的快速库建议都可以吗?

3 个答案:

答案 0 :(得分:9)

听起来你想要MessageFormat

来自文档:

  

以下示例创建一个可以重复使用的MessageFormat实例:

 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));

答案 1 :(得分:2)

如果您正在寻找快速实施,则需要在JDK之外查看。您可能仍然使用slf4j进行日志记录,因此请查看其MessageFormatter

MessageFormatter.arrayFormat("x:{} y:{} z:{}", new Object[] {i, i, i}).getMessage();

在我的机器上(以及原始且有缺陷的微基准标记),它比FastFormat类慢约1/6,比String::format或{快约5-10倍{1}}。

答案 2 :(得分:1)

我说我愿意,而且在这里。我的预编译能力字符串格式(工作概念证明)库:https://gitlab.com/janecekpetr/string-format

使用

StringFormat.format("x:{} y:{} z:{}", i, i, i)

我得到的数字与slf4j和log4j2非常相似。

但是,使用时

CompiledStringFormat format = StringFormat.compile("x:{} y:{} z:{}");

// and then, in the loop
format.format(i, i, i)

我得到的数字比你的FastFormat大1/3。请注意,此时,您必须格式化大量字符串才能获得显着差异。