使用{}格式化替代字符串拼接的Logger slf4j优点

140

使用{}与字符串拼接相比,有什么优势吗?

以下是slf4j的一个示例:

logger.debug("Temperature set to {}. Old temperature was {}.", t, oldT);

替代

logger.debug("Temperature set to"+ t + ". Old temperature was " + oldT);

我认为这与速度优化有关,因为根据配置文件,在运行时可以避免参数评估(和字符串连接)。但只能使用两个参数,有时别无选择,只能使用字符串连接。需要对此问题进行讨论。


https://www.slf4j.org/faq.html#logging_performance - Grigory Kislin
7个回答

91

这是关于字符串拼接性能的问题。如果你有密集的日志记录语句,那么这可能是非常重要的。

(在 SLF4J 1.7 之前) 但只有两个参数是可能的

由于绝大多数日志记录语句仅具有2个或更少的参数,因此SLF4J API在版本1.6及以下版本中已经涵盖了大部分使用情况。自API版本1.7以来,API设计者提供了带有变长参数的重载方法。

对于那些需要超过2个参数且受限于使用先于1.7版本的SLF4J的情况,可以使用字符串连接或new Object[] { param1, param2, param3, ... }。这样的情况应该很少,因此性能并不重要。


3
应避免未使用的字符串连接(即调试语句)。请使用过度冗长但高效的日志级别检查或较小但可能带来轻微开销的对象数组参数。如果一切条件相等,我更喜欢后者。很难说字符串连接不重要/不影响性能。理论上,对象数组创建可以内联和优化掉,并且“真正”不会有任何区别(与一厢情愿的想法相反)。这不是过早优化,而只是第一次正确/更好地执行某些操作的问题。 - michael
1
为什么不对System.out.println()进行重载修改,使其类似于slf4j的logger,从而避免字符串连接? - a3.14_Infinity

63

简短版:是的,它更快,代码也更少!

字符串拼接会做很多不必要的工作(传统的“是否启用调试”测试来自 log4j),如果可能的话应该避免使用。通过 {},可以将 toString() 调用和字符串构造推迟到决定是否需要捕获事件之后。在我的看法中,通过让记录器格式化一个 单一 字符串,代码变得更加清晰。

你可以提供任意数量的参数。请注意,如果你使用旧版本的 sljf4j 并且有超过两个参数传递给 {},则必须使用 new Object[]{a,b,c,d} 语法来传递数组。参见例如:http://slf4j.org/apidocs/org/slf4j/Logger.html#debug(java.lang.String, java.lang.Object[])

关于速度:Ceki 在其中一个列表上发表了一篇基准测试文章。


8
最新的Javadoc显示了更现代化的可变参数语法,debug(String format, Object... arguments)。请参见http://slf4j.org/faq.html#logging_performance。 - michael
因为提到了.toString()的评估以及连接性能,所以被点赞。这是发生在记录器内部的事情,记录器可以决定是否需要调用该方法。如果未达到日志级别,则不会执行该操作。 - Chetan Narsude

8

在Java中,由于字符串是不可变的,因此每次连接一对字符串时,左侧和右侧字符串都必须被复制到新字符串中。因此,最好使用占位符。


6
如果只有一对,这种方法是正确的,但通常是错误的,因为编译器将连接转换为字符串构建器调用,生成更快且不需要进行过多内存分配的代码。 - cdeszaq

3

另一个替代方案是String.format()。我们在jcabi-log中使用它(作为slf4j的静态实用程序包装器)。

Logger.debug(this, "some variable = %s", value);

它更易于维护和扩展。另外,翻译也很容易。


4
我不认为这样更易于维护。如果“value”的类型发生了改变,你必须回去修改日志记录语句。而这不是IDE能够帮助你的。日志应该辅助调试,而不是妨碍它。 :-) - Chetan Narsude
3
至少在IntelliJ 2016中,当格式字符串与格式化参数不匹配时会提醒用户。例如:String.format("%d", "Test")会产生IntelliJ警告Argument type 'String' does not match the type of the format specifier '%d'.。不过,我不确定它是否能够在使用上述解决方案时仍能提供这种智能回应。 - crush
这个的速度是多少? - Thorbjørn Ravn Andersen
@ThorbjørnRavnAndersen,它内部相当原始,但当然比静态记录器慢。 - yegor256
2
包装slf4j?那不是违背使用slf4j的初衷吗?另外,我看到很多人错误地使用String.format,导致字符串在日志级别评估之前被格式化,例如:logger.info(String.format("hello %s", username))。 - Juan Bustamante
显示剩余3条评论

2

从作者的角度来看,主要原因是减少字符串连接的开销。我刚刚阅读了记录器的文档,你可以找到以下文字:

/**
* <p>This form avoids superfluous string concatenation when the logger
* is disabled for the DEBUG level. However, this variant incurs the hidden
* (and relatively small) cost of creating an <code>Object[]</code> before 
  invoking the method,
* even if this logger is disabled for DEBUG. The variants taking
* {@link #debug(String, Object) one} and {@link #debug(String, Object, Object) two}
* arguments exist solely in order to avoid this hidden cost.</p>
*/
*
 * @param format    the format string
 * @param arguments a list of 3 or more arguments
 */
public void debug(String format, Object... arguments);

1

连接操作很耗费时间,因此您只希望在需要时才进行连接。通过使用{},slf4j仅在需要跟踪时执行连接操作。在生产环境中,您可以将日志级别配置为INFO,从而忽略所有调试跟踪。

像这样的跟踪即使将被忽略也会连接字符串,这是浪费时间的:

logger.debug("Temperature set to"+ t + ". Old temperature was " + oldT);

像这样的跟踪将被免费忽略:

logger.debug("Temperature set to {}. Old temperature was {}.", t, oldT);

如果您有许多在生产中忽略的调试跟踪,则使用{}肯定更好,因为它不会影响性能。


0

合规的日志记录对应用程序开发非常重要,因为它会影响性能。

上述不合规的日志记录导致每次调用都会产生冗余的toString()方法调用,并导致冗余的临时内存分配和CPU处理,例如在高规模测试执行中可以看到冗余分配的临时内存: Memory

查看方法分析:
Look on method profiling

注意:我是这篇博客文章Logging impact on application performance的作者。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接