【问题标题】:String concatenation performance in Log4jLog4j 中的字符串连接性能
【发布时间】:2018-05-02 22:05:21
【问题描述】:

我经常听到人们说,在记录时避免使用String Concatenation 而使用{} 是最佳实践之一。

我正在查看Log4j 代码,看看他们是如何处理这个问题的,并认为他们正在做类似的事情。

这是format() 方法的 sn-p 方法,它采用模式和参数并返回要记录的消息。

/**
 * Formats arguments using SLF4J-like formatter.
 * @param pattern pattern, may be malformed.
 * @param arguments arguments.
 * @return Message string
 */
private static String format(final String pattern,
                             final Object[] arguments) {
    if (pattern != null) {
        String retval = "";
        int count = 0;
        int prev = 0;
        int pos = pattern.indexOf("{");
        while(pos >= 0) {
            if (pos == 0 || pattern.charAt(pos-1) != '\\') {
                retval += pattern.substring(prev, pos);
                if (pos + 1 < pattern.length() && pattern.charAt(pos+1) == '}') {
                    if(arguments != null && count < arguments.length) {
                        retval += arguments[count++];
                    } else {
                        retval += "{}";
                    }
                    prev = pos + 2;
                } else {
                    retval += "{";
                    prev = pos + 1;
                }
            } else {
                retval += pattern.substring(prev, pos - 1) + "{";
                prev = pos + 1;
            }
            pos = pattern.indexOf("{", prev);
        }
        return retval + pattern.substring(prev);
    }
    return null;
}

我无法理解这种实现比使用串联更好。对此的任何见解都会非常有帮助。

【问题讨论】:

  • 如果你有一个超长的字符串需要用“+”分隔以提高可读性,会发生什么。但是您仍然保留“{}”占位符。 logger.debug("Very very very very very very very very very very very very very" + " very very very very long {}", textName);

标签: java performance logging log4j


【解决方案1】:

在日志系统中格式化字符串的好处是,日志系统可以决定字符串连接是否必须发生。

让我们以这些行为例:

log.debug("Count: " + list.size());
log.debug("Count: {}", list.size());

只要这个 logger 的级别是 debug 或以下,性能上没有区别,但是如果 log level 高于 debug 则第二行根本不会执行连接。

【讨论】:

  • 如果你有一个超长的字符串需要用“+”分隔以提高可读性,会发生什么。但是您仍然保留“{}”占位符。 logger.debug("Very very very very very very very very very very very very very" + " very very very very long {}", textName);
  • @PaulErlenmeyer 这不是问题,这个常量字符串连接会被编译器提前执行。
  • 那么只有方法调用和字符串之间的连接才重要?
  • 字符串文字和/或字符串常量之间的连接很好,因为它们可以由编译器提前执行。当涉及到方法调用、局部变量或非静态字段等非常量表达式时,它会变得“糟糕”,其中必须在运行时执行连接。
【解决方案2】:

这个问题的一些答案解释了:

简短的版本是基于格式的使用更快,因为在

   Logger.debug("my name is {}", name);

昂贵的字符串攻击只发生在之后 log4j 决定需要记录事件;例如在基于日志记录级别等过滤之后。

相比之下,用字符串拼接的版本

   Logger.debug("my name is " + name);

在评估参数时会发生字符串抨击。因此,即使在没有实际记录事件的情况下也会发生这种情况。您可以通过在代码中添加“守卫”来部分避免这种情况(见下文),但这样做会使您的应用程序记录调用变得冗长。


但是看看这个例子:

   log.debug("Count: " + list.size());
   log.debug("Count: {}", list.size());

格式版本会更快,但两个版本总是评估list.size() 表达式。如果这是一项昂贵的操作,那么您可能需要求助于使用警卫;例如

   if (log.isDebugEnabled()) {
       log.debug("Count: " + list.size());
   }

【讨论】:

  • 如果启用特定的日志记录级别(在这种情况下为debug)不会有任何性能优势?
  • 正确。但人们通常不会在生产环境中使用调试级别日志记录运行代码。
【解决方案3】:

不执行 list.size() 的替代方案:

// Instead of
if (log.isDebugEnabled()) {
   log.debug("Count: " + list.size());
}

// Use if >= Java 8
log.debug("Count: {}", () -> list.size());

这将仅在需要时评估 list.size()。

信用:https://garygregory.wordpress.com/2015/09/16/a-gentle-introduction-to-the-log4j-api-and-lambda-basics/

【讨论】:

    猜你喜欢
    • 2010-09-12
    • 2010-10-03
    • 1970-01-01
    • 1970-01-01
    • 2022-09-30
    • 1970-01-01
    • 2013-09-10
    • 2014-06-05
    • 2015-10-14
    相关资源
    最近更新 更多