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

Raf*_*ian 7 java lambda logging

许多日志记录框架(例如,log4j)允许您将lambda表达式而不是Strings传递给日志记录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);
Run Code Online (Sandbox Code Playgroud)

让我们假设它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);
Run Code Online (Sandbox Code Playgroud)

但是,编译器可以内联日志记录方法以使净效果如下所示是可行的:

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

在这种情况下,我们不必在日志API调用之前评估字符串(因为没有),并且可能,我们将从内联中获得性能.

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

Jon*_*eet 9

您的优化不仅仅引入了内联 - 它改变了排序.这通常不是有效的.

特别是,除非JIT能够证明这些方法没有其他效果,否则更改是否调用方法是无效的.如果JIT编译器内联并重新排序到那个程度,我会感到非常惊讶- 检查构造该方法的参数所涉及的所有操作没有副作用的成本在大多数情况下可能不值得.(JIT编译器无法以不同于其他方法的方式处理日志记录方法.)

因此,尽管它可能对于一个非常,非常聪明的JIT编译器要做到这一点,我会很惊讶地看到任何真正做到这一点.如果你发现自己正在使用它,并编写测试来证明这种方法并不比使用lambda表达式更昂贵,并且继续证明随着时间的推移,这很好 - 但听起来你更愿意假设情况如此,我肯定不会.