我可以在 String.format 中预编译格式字符串吗? (或者做任何其他事情来更快地格式化日志?)
Can I precompile the format string in String.format? (Or do any other thing to make formatting logs faster?)
众所周知that String.format() performance is terrible。我在我的(可能很常见的)典型案例中看到了很大的可能改进。我多次打印相同的数据结构。让我们想象一下 "x:%d y:%d z:%d" 这样的结构。我预计 String.format() 的主要问题是它必须始终解析格式字符串。我的问题是:是否有一些现成的 class 允许只读取一次格式化字符串,然后允许在填充可变参数时快速给出字符串?用法应如下所示:
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);
}
我知道这是可能的 - 以下是我的快速和肮脏的例子,它做我正在谈论的事情并且在我的机器上快大约 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
[编辑]
As Steve Chaloner replied there is a 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 replied there is 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();
}
使用上面 [编辑] 部分给出的 MessageFormat 代码。我确实得到了以下循环 1M 次的结果:
Time MessageFormatter [s]: 1.099 880 912
Time MessageFormat [s]: 2.631 521 135
speed up : 2.393 times
所以 MessageFormatter 是迄今为止最好的答案,但我的简单示例仍然快一点...所以有任何现成的更快的库提案吗?
听起来你想要MessageFormat
来自文档:
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));
如果您正在寻找快速实施,则需要查看 JDK 之外的内容。您可能使用 slf4j for logging anyway, so let's look at its MessageFormatter
:
MessageFormatter.arrayFormat("x:{} y:{} z:{}", new Object[] {i, i, i}).getMessage();
在我的机器上(以及一个粗糙且有缺陷的微基准测试),它比你的 FastFormat
class 慢大约 1/6,比任何一个 String::format
快大约 5-10 倍或 MessageFormat
.
我说我会送货,现在就送来了。我的预编译字符串格式化(工作概念验证)库: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。请注意,此时,您必须格式化大量字符串才能获得显着差异。
众所周知that String.format() performance is terrible。我在我的(可能很常见的)典型案例中看到了很大的可能改进。我多次打印相同的数据结构。让我们想象一下 "x:%d y:%d z:%d" 这样的结构。我预计 String.format() 的主要问题是它必须始终解析格式字符串。我的问题是:是否有一些现成的 class 允许只读取一次格式化字符串,然后允许在填充可变参数时快速给出字符串?用法应如下所示:
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);
}
我知道这是可能的 - 以下是我的快速和肮脏的例子,它做我正在谈论的事情并且在我的机器上快大约 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
[编辑]
As Steve Chaloner replied there is a 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 replied there is 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();
}
使用上面 [编辑] 部分给出的 MessageFormat 代码。我确实得到了以下循环 1M 次的结果:
Time MessageFormatter [s]: 1.099 880 912
Time MessageFormat [s]: 2.631 521 135
speed up : 2.393 times
所以 MessageFormatter 是迄今为止最好的答案,但我的简单示例仍然快一点...所以有任何现成的更快的库提案吗?
听起来你想要MessageFormat
来自文档:
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));
如果您正在寻找快速实施,则需要查看 JDK 之外的内容。您可能使用 slf4j for logging anyway, so let's look at its MessageFormatter
:
MessageFormatter.arrayFormat("x:{} y:{} z:{}", new Object[] {i, i, i}).getMessage();
在我的机器上(以及一个粗糙且有缺陷的微基准测试),它比你的 FastFormat
class 慢大约 1/6,比任何一个 String::format
快大约 5-10 倍或 MessageFormat
.
我说我会送货,现在就送来了。我的预编译字符串格式化(工作概念验证)库: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。请注意,此时,您必须格式化大量字符串才能获得显着差异。