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

时间:2017-12-06 20:17:43

标签: java logging lambda

许多日志记录框架(例如,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情况下,如果日志级别不匹配,则不会对字符串求值。

3 个答案:

答案 0 :(得分:9)

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

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

因此,虽然可能是一个真正非常聪明的JIT编译器来实现这一点,但我会非常惊讶地看到实际这样做了。如果您发现自己正在使用一个,并且编写测试证明这种方法并不比使用lambda表达式更昂贵,并且继续证明随着时间的推移,这很好 - 但听起来像你是我认为情况确实如此,我绝对不会这样做。

答案 1 :(得分:1)

Raffi让我们看看一个例子,说明你正在讨论的编译器如何改变程序逻辑,编译器需要非常聪明才能弄清楚:

waitpid

1)无需内联, public String process(){ //do some important bussiness logic return "Done processing"; } 将被调用,无论日志记录级别如何:

process()

2)内联log( Level.FINE, "Very expensive string to construct ..." + process() ); 只会在特定日志记录级别下调用,而且我们的重要业务逻辑将无法运行:

process()

在这种情况下,编译器必须弄清楚如何创建消息字符串,如果在创建过程中调用任何其他方法,则不要内联该方法。

答案 2 :(得分:0)

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

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

 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.LoggerJavaDoc中所述:

  

记录一条消息,仅在记录级别为时才构建   这样就可以实际记录消息。

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