简体   繁体   English

减少条件跟踪/记录调用的开销

[英]Overhead reduction of conditional trace/logging calls

For tracing and debugging my Java code, I am using a simple Util class rather than a full-blown logging framework: 为了跟踪和调试Java代码,我使用了一个简单的Util类,而不是一个成熟的日志框架:

public class Util {
    public static int debugLevel;

    public static void info(String msg) {
      //  code for logfile output
      //  handling of unprintable characters
      //  etc. omitted 
      System.out.println(msg);
    }

    public static void info4(String msg) {
        if (debugLevel >= 4) {
           info(msg);
        }
    }
}

This allows compact single-line statements like the following: 这允许紧凑的单行语句,如下所示:

info4(String.format("%16s: ", host) + Util.toHex(sidNetto));

Using a debugLevel variable, I can control the verbosity of my program. 使用debugLevel变量,我可以控制程序的详细程度。 Usually, the debug level is set globally at execution start. 通常,调试级别是在执行开始时全局设置的。 But it can also be adjusted locally on routine level. 但是它也可以在常规级别上进行本地调整。

Basically, I save the repeated if (debugLevel >= DEBUG_ALL) {...} brackets around my trace calls. 基本上,我将重复的if (debugLevel >= DEBUG_ALL) {...}括号保存在跟踪调用周围。 However, the parameters of the call have to be prepared and passed at runtime regardless of the debug level. 但是,无论调试级别如何,都必须在运行时准备并传递调用的参数。

My question: 我的问题:

How could I nudge the compile-time optimizer or the JVM to remove superfluous trace calls? 我怎样才能轻推编译时优化器或JVM来删除多余的跟踪调用? I am thinking on the lines of C/C++ function inlining. 我在考虑C/C++函数内联。

A related question regarding C# was discussed here . 这里讨论了有关C#一个相关问题。 But I am not sure how to port the suggested answers to Java . 但是我不确定如何将建议的答案移植到Java Another related post back from 2010 discussed similar approaches to mine. 2010年的另一篇相关文章讨论了类似的方法。 Im wondering if third-party tools like ProGuard are actually required do solve such a common task. 我想知道是否真的需要像ProGuard这样的第三方工具来解决这样一个常见的任务。

This is how most logging frameworks do it. 大多数日志记录框架就是这样做的。 For lightweight arguments (this includes built-in formatters which is a good best practice) do not check the level, otherwise check the level before serialising complicated string arguments. 对于轻量级参数(最好是包括内置格式化程序),请不要检查级别,否则请在序列化复杂的字符串参数之前检查级别。

You could use Java 8 java.util.functions.Supplier<String> for lacy evaluation, but I think there might be no performance to gain over the explicite level test case. 您可以使用Java 8 java.util.functions.Supplier<String>进行花边评估,但是我认为在显式级别的测试用例上可能无法获得任何性能。

The logger would look like: 记录器如下所示:

void debug(String ptrn, Supplier<String> args...)

And you can use it like: 您可以像这样使用它:

debug("Hello {0}", this::getName());

Most logging APIs that I know suggest to check if the log level is enabled before actually calling the log method in case the message has to be prepared first, eg: 我知道的大多数日志记录API建议在实际调用log方法之前先检查是否启用了日志级别,以防必须先准备消息,例如:

if (logger.isTraceEnabled()) {
    String msg = String.format("Name changed from %s to %s", oldName, newName);
    logger.trace(msg);
}

Some logging APIs like SLF4J also provide more complex log methods that accept a format string and multiple arguments, so that the log message is only built in case the log level is enabled: 一些日志记录API(例如SLF4J)还提供了更复杂的日志记录方法,这些方法接受格式字符串和多个参数,因此仅在启用日志级别的情况下才生成日志消息:

logger.trace("Name changed from {} to {}", oldName, newName);

This is sufficient in most of the cases, but sometimes your message is more complex to build, or the arguments have to be converted to strings first. 在大多数情况下,这已经足够了,但是有时您的消息构建起来会更复杂,或者必须先将参数转换为字符串。 In this case, checking the log level is still a good approach. 在这种情况下,检查日志级别仍然是一种好方法。

Since Java 8, you could also take advantage of lambda expressions to solve this issue. 从Java 8开始,您还可以利用Lambda表达式解决此问题。 Your log method could be implemented like that: 您的日志方法可以这样实现:

public void log(Supplier<String> messageSupplier) {
    if (isLogEnabled()) {
        String msg = messageSupplier.get();
        // TODO: log msg
    }
}

As you can see, the message is retrieved form the messageSupplier only in case logging is enabled. 如您所见,仅在启用日志记录的情况下,才从messageSupplier检索消息。 Thanks to lambda expressions, implementing a Supplier<String> is very easy: 多亏了lambda表达式,实现Supplier<String>非常容易:

logger.log(() -> String.format("Name changed from %s to %s", oldName, newName));

Update (thanks to Joshua Taylor) 更新(感谢Joshua Taylor)

Since Java 8, the java.util.logging API already supports message suppliers, eg see Logger#info , so you could easily exchange your logging implementation by the 'on-board' solution from JRE. 从Java 8开始,java.util.logging API已经支持消息提供者,例如,请参阅Logger#info ,因此您可以通过JRE的“内置”解决方案轻松地交换日志记录实现。

It seems weird to not use established logging framework due to their complexity, but worry about minor optimizations like method inlining, while ignoring the greater problem of formatting a log string irrespective of log level. 由于它们的复杂性,似乎不使用已建立的日志记录框架很奇怪,但是担心诸如方法内联之类的较小优化,却忽略了格式化日志字符串的更大问题,而与日志级别无关。 But if you insist on reinventing the wheel: 但是,如果您坚持重新发明轮子:

The JVM (at least the Oracle hotspot JVM) automatically inlines short methods, and performs dead code elimination of unreachable branches. JVM(至少是Oracle热点JVM)自动内联短方法,并对无法访问的分支执行无效代码消除。 To be detected as unreachable, the log level of the message and the level threshold would have to be constant (compile-time constant, or static final). 要检测为无法到达,消息的日志级别和级别阈值必须是恒定的(编译时常数或静态最终值)。 Otherwise, the JVM will compare logging levels on each call, though it is still likely to perform speculative inlining (inline the branch usually taken, guarded by a conditional branch instruction) which ensures that a branch instruction is only executed in the unusual case. 否则,JVM仍将比较每次调用的日志记录级别,尽管它仍可能执行推测性内联(内联通常采用的分支,由条件分支指令保护),以确保仅在异常情况下执行分支指令。

A much greater concern however is the cost of building the log message, which should only be incurred if the message must actually be logged. 但是,更令人担忧的是构建日志消息的成本,只有在必须实际记录该消息的情况下才产生该消息。 The old log4j approach of requiring calling code to check whether logging is enabled before preparing the message is rather verbose and easily forgotten. 在准备消息之前,要求调用代码检查是否已启用日志记录的旧log4j方法相当冗长,很容易被遗忘。 Instead, SLF4J defers string concatenation to the logging system by having the log methods take a format string and a variable number of objects to be inserted into placeholders. 相反,SLF4J通过使log方法采用格式字符串和可变数量的要插入占位符的对象,将字符串连接推迟到日志记录系统。 The SLF4J FAQ writes : SLF4J常见问题解答写道

The following two lines will yield the exact same output. 以下两行将产生完全相同的输出。 However, the second form will outperform the first form by a factor of at least 30, in case of a disabled logging statement. 但是,在禁用日志记录语句的情况下,第二种形式的性能将比第一种形式高至少30倍。

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

It is worth noting that the arguments (here: entry ) are of type Object , so their conversion to String only happens if the message actually has to be logged. 值得注意的是,参数(此处为entry )的类型为Object ,因此仅当实际上必须记录消息时才将其转换为String

To be clear, there is no reliable way to skip evaluation of method arguments by redefining a method, because such elimination may only occur if the just in time compiler can prove the evaluation to be side effect free, which the hotspot jvm only detects if it has inlined the entire evaluation, which it will only to for very simple evaluations. 需要明确的是,没有可靠的方法可以通过重新定义方法来跳过对方法参数的求值,因为只有在即时编译器可以证明该求值没有副作用的情况下,这种消除才可能发生,热点jvm仅在其发现时才进行检测已内联整个评估,仅用于非常简单的评估。 Therefore, the API solution of moving formatting into the logging system is probabaly the best you can hope for. 因此,将格式转移到日志记录系统中的API解决方案可能是您所希望的最好的解决方案。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM