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 情况下,如果日志记录级别不匹配,则不会评估字符串。
特别是,更改是否调用方法是无效的,除非 JIT 能够
证明这些方法没有其他效果。如果 JIT 编译器能够内联并重新排序到这种程度,我会感到非常惊讶 - 在大多数情况下,检查构造方法参数所涉及的所有操作是否没有副作用的成本可能不值得。 (JIT 编译器无法将日志记录方法与其他方法区别对待。) 因此,虽然真正非常智能的 JIT 编译器“可能”能够做到这一点,但如果看到任何“实际上”做到了这一点,我会感到非常惊讶。如果您发现自己正在使用一种方法,并编写测试来证明
这种方法并不比使用 lambda 表达式更昂贵,并且随着时间的推移继续证明这一点,那就太好了 - 但听起来您更热衷于假设这就是情况,我绝对不会。
Raffi 让我们看一个示例,了解您所讨论的内联编译器将如何改变程序逻辑,并且编译器需要足够聪明才能弄清楚这一点:
public String process() {
//do some important business logic
return "Done processing";
}
process()
通过内联,
process()
只会在特定的日志级别下被调用,我们的重要业务逻辑将无法运行:if (Level.FINE >= System.logLevel) System.out.println("构建非常昂贵的字符串..." + process() );
在这种情况下,编译器必须弄清楚消息字符串是如何创建的,并且如果在创建过程中调用任何其他方法,则不能内联该方法。
这种优化内联仅适用于您提供的非常简单的示例(当它只是
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.Logger
JavaDoc中所说:
记录一条消息,只有当日志级别为 这样消息就会被实际记录。
所以这是一个目的:如果你可以避免构造,那么你就不需要执行这些计算并将结果作为参数传递。