如果编译器可以内联日志记录调用,为什么还要在日志记录 API 中使用 lambda 表达式呢?

问题描述 投票:0回答:3
许多日志记录框架(例如 log4j)允许您将 lambda 表达式而不是

String

 传递给日志记录 API。争论的焦点是,如果字符串的构造特别具有表现力,则可以通过 lambda 表达式延迟执行字符串构造。这样,只有系统的日志级别与调用的日志级别匹配时才会构造字符串。

但是,鉴于现代编译器自动执行许多方法内联,以这种方式使用 lambda 表达式真的有意义吗?我将在下面提供一个简化的示例来证明这个问题。

假设我们传统的日志记录方法是这样的:

void log(int level, String message) { if (level >= System.logLevel) System.out.println(message); } // .... System.logLevel = Level.CRITICAL; log(Level.FINE, "Very expensive string to construct ..." + etc);

假设

FINE

小于
CRITICAL
,因此,虽然构造了一个昂贵的字符串,但由于消息没有输出,所以一切都没有。

Lambda 日志记录 API 可帮助解决这种情况,以便仅在必要时评估(构造)字符串:

void log(int level, Supplier<String> message) { if (level >= System.logLevel) System.out.println(message.apply()); } // .... System.logLevel = Level.CRITICAL; log(Level.FINE, () -> "Very expensive string to construct ..." + etc);

但是,编译器可以直接内联日志记录方法,这样最终的效果如下:

System.logLevel = Level.CRITICAL; if (Level.FINE >= System.logLevel) System.out.println("Very expensive string to construct..." + etc);

在这种情况下,我们不必在日志 API 调用之前评估字符串(因为没有),并且大概我们可以仅通过内联来获得性能。

总而言之,我的问题是,鉴于编译器可能内联记录 API 调用,lambda 表达式在这种情况下如何帮助我们?我唯一能想到的是,不知何故,在 lambda 情况下,如果日志记录级别不匹配,则不会评估字符串。

java logging lambda
3个回答
9
投票
您的优化不仅仅引入了内联 - 它改变了顺序。这通常不成立。

特别是,更改是否调用方法是无效的,除非 JIT 能够

证明这些方法没有其他效果。如果 JIT 编译器能够内联并重新排序到这种程度,我会感到非常惊讶 - 在大多数情况下,检查构造方法参数所涉及的所有操作是否没有副作用的成本可能不值得。 (JIT 编译器无法将日志记录方法与其他方法区别对待。) 因此,虽然真正非常智能的 JIT 编译器“可能”能够做到这一点,但如果看到任何“实际上”做到了这一点,我会感到非常惊讶。如果您发现自己正在使用一种方法,并编写测试来证明

这种方法并不比使用 lambda 表达式更昂贵,并且随着时间的推移继续证明这一点,那就太好了 - 但听起来您更热衷于假设这就是情况,我绝对不会。

Raffi 让我们看一个示例,了解您所讨论的内联编译器将如何改变程序逻辑,并且编译器需要足够聪明才能弄清楚这一点: public String process() { //do some important business logic return "Done processing"; }


1
投票
如果没有内联,无论日志记录级别如何,都将调用

process()

log( Level.FINE, "构建非常昂贵的字符串..." + process() );
  1. 通过内联,
    process()

    只会在特定的日志级别下被调用,我们的重要业务逻辑将无法运行:

    if (Level.FINE >= System.logLevel) System.out.println("构建非常昂贵的字符串..." + process() );
  2. 在这种情况下,编译器必须弄清楚消息字符串是如何创建的,并且如果在创建过程中调用任何其他方法,则不能内联该方法。

    这种优化内联仅适用于您提供的非常简单的示例(当它只是
  3. String
连接时)。

事实上,这个API可以有更复杂的使用方式:

0
投票
public void log(Level level, Supplier<String> msgSupplier)

假设我有一个专门的供应商,它执行相当昂贵的日志消息生成:

Supplier<String> supplier = () -> { // really complex stuff };

然后我在几个地方使用它:

LOGGER.log(Level.SEVERE, supplier); ... LOGGER.log(Level.SEVERE, supplier);

那么,你会内联什么?展开-内联它到

System.logLevel = Level.CRITICAL; if (Level.FINE >= System.logLevel) System.out.println(supplier.get());

没有任何意义。

正如

java.util.logging.Logger

JavaDoc
中所说:

记录一条消息,只有当日志级别为 这样消息就会被实际记录。



所以这是一个目的:如果你可以避免构造,那么你就不需要执行这些计算并将结果作为参数传递。
    

© www.soinside.com 2019 - 2024. All rights reserved.