【问题标题】:Which of array creation vs string concatenation for logging method has better performance?记录方法的数组创建与字符串连接哪个性能更好?
【发布时间】:2015-07-31 13:44:11
【问题描述】:

在传递多个字符串进行日志记录时,最好使用可变参数还是字符串连接?

日志将在生产环境中被禁用。

考虑下面的代码,

public void log(int logLevel, String ... msg) {
    if (logLevel >= currentLogLevel) {
        for (String m : msg) {
            // print the log to file
        }
    }
}
// calling
log(2, "text1", var1, "text2");

对比

public void log(int logLevel, String msg) {
    if (logLevel >= currentLogLevel) {
        // print the log to file
    }
}
// calling
log(2, "text1" + var1 + "text2");

哪一个在两种情况下都表现良好(启用/禁用)?

【问题讨论】:

  • 每当我们进行字符串操作时,它都会创建一个新字符串。字符串操作是资源消耗。

标签: java performance logging


【解决方案1】:

这些都不是特别好。可变参数的性能会更好,因为它不执行String 连接,这比创建数组的操作要昂贵得多,但是因为您使用的是String 可变参数而不是Object 可变参数,所以toString() 方法将必须在所有对象上调用即使在生产中,这将相对昂贵。

但是,不要害怕!这是一个已解决的问题SLF4J (Simple Logging Facade for Java) 已经为您找到了最好的方法。如果你使用 SLF4J 实现(最常见的两个是 Log4JLogback),你可以使用 parameterized logging arguments

更好的选择

存在一种基于消息格式的便捷替代方案。假设 entry 是一个对象,你可以这样写:

Object entry = new SomeObject(); 
logger.debug("The entry is {}.", entry);

只有在评估是否记录之后,并且只有在决定是肯定的情况下,记录器实现才会格式化消息并将“{}”对替换为条目的字符串值。也就是说,这种形式在日志语句被禁用的情况下,不会产生参数构造的成本。

以下两行将产生完全相同的输出。但是,在禁用日志记录语句的情况下,第二个变体将优于第一个变体至少 30 倍。

logger.debug("The new entry is "+entry+".");
logger.debug("The new entry is {}.", entry);

也可以使用两个参数变体。例如,你可以写:

logger.debug("The new entry is {}. It replaces {}.", entry, oldEntry);

如果需要传递三个或更多参数,也可以使用 Object[] 变体。例如,你可以写:

Object[] paramArray = {newVal, below, above};
logger.debug("Value {} was inserted between {} and {}.", paramArray);

请注意,在第一种和第二种情况下,它不会创建临时数组,如果在 SLF4J 中不需要,则存在方法签名以不创建数组。 Here is the full documentation where you can see all the different method signatures

如果您决定使用Logback,您应该知道关闭日志记录时调用方法的成本约为 20 纳秒。 Full discussion of Logback's performance:

您可以通过将根记录器的级别设置为 Level.OFF(可能的最高级别)来完全关闭日志记录。当完全关闭日志记录时,日志请求的成本包括方法调用和整数比较。在 3.2Ghz Pentium D 机器上,此成本通常约为 20 纳秒。

【讨论】:

  • 你应该先做合理的基准测试,然后再声称它有这样的不同。
  • @Holger 我在基准数据中进行了编辑。这些库的维护者之前已经这样做了。
  • 您链接的页面上没有基准。那里的陈述与您的重复一样没有依据。
  • @durron597 toString() 如果是对象将被调用,如果是字符串则不会。但是真正的日志实现需要对象类型来支持完整的日志记录。我已经只使用 Object varargs 来打印异常和其他对象。
【解决方案2】:

从 Java-8 开始,使用 Supplier<String> 有一个更快的替代方案:

public void log(int logLevel, Supplier<String> msg) {
    if (logLevel >= currentLogLevel) {
        // print the log to file like out.println(msg.get());
    }
}

// calling
log(2, () -> "text1" + var1 + "text2");

如果您多次执行此行(第一次速度较慢,因为应该创建匿名类),这比串联要快得多。资料来源:Aleksey Shipilëv talk on Joker'14(PDF,查看幻灯片 26-28)。

【讨论】:

    【解决方案3】:

    如果您使用SLF4J 之类的东西,您可以执行参数化日志消息。像这样

    log.info("Item {} is done, it took {} ms", item.getName(), duration);
    

    这还有一个好处,那就是不必在日志级别检查if(log.isDebugEnabled()) 中包装其中的一些。见this faq

    【讨论】:

    • 这是一个可变参数调用,因此与 OP 已经描述的替代方案之一没有本质区别。
    • SLF4J 将其称为参数化日志功能,正如您所说,它使用可变参数调用。
    • SLF4J 不会调用可变参数方法签名,除非参数超过三个。 slf4j.org/api/org/slf4j/Logger.html
    猜你喜欢
    • 1970-01-01
    • 2012-06-27
    • 2016-04-29
    • 2014-11-28
    • 2021-02-24
    • 2010-09-23
    • 2023-03-11
    • 1970-01-01
    相关资源
    最近更新 更多