Why bother using lambda expressions in logging API

2019-03-27 11:37发布

Many logging frameworks (e.g., log4j) allow you to pass lambda expressions instead of Strings to the logging API. The argument is that if the string is particularly expressive to construct, the string construction can be lazily executed via the lambda expression. That way, the string is only constructed if the system's log level matches that of the call.

But, given that modern compilers do much method inlining automatically, is there really a point to using lambda expressions in this way? I'll supply a simplified example below to demonstrate this concern.

Suppose our traditional logging method looks like this:

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);

Let's suppose that FINE is less than CRITICAL, so, although an expensive string is constructed, it's all for not since the message is not outputted.

Lambda logging APIs help this situation so that the string is only evaluated (constructed) when necessary:

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);

But, it's feasible that the compiler can just inline the logging method so that the net effect is as follows:

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

In this case, we don't have to evaluate the string prior to the logging API call (because there is none), and presumably, we would gain performance from just the inlining.

In summary, my question is, how do lambda expressions help us in this situation given that the compiler can possibly inline logging API calls? The only thing I can think of is that, somehow, in the lambda case, the string is not evaluate if the logging level is not a match.

3条回答
Summer. ? 凉城
2楼-- · 2019-03-27 11:56

This kind of optimisation-inlining would work for only really simple examples like you have provided (when it is just String concatenation).

In fact, this API can be used in more sophisticated way:

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

Let's say I have a dedicated supplier, which performs a quite expensive log-message producing:

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

and then I use it in several places:

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

Then, what would you inline? Unwrapping-inlining it into

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

doesn't make any sense.

As it said in java.util.logging.Logger class JavaDoc:

Log a message, which is only to be constructed if the logging level is such that the message will actually be logged.

So this is a purpose: if you can avoid construction, that you don't need to perform these calculations and pass the result as parameter.

查看更多
手持菜刀,她持情操
3楼-- · 2019-03-27 11:57

Your optimization hasn't just introduced inlining - it's changed ordering. That's not generally valid.

In particular, it wouldn't be valid to change whether methods are called, unless the JIT can prove that those methods have no other effect. I'd be very surprised if a JIT compiler would inline and reorder to that extent - the cost of checking that all the operations involved in constructing the argument to the method have no side effects is probably not worth the benefit in most cases. (The JIT compiler has no way of treating logging methods differently to other methods.)

So while it's possible for a really, really smart JIT compiler to do this, I'd be very surprised to see any that actually did this. If you find yourself working with one, and write tests to prove that this approach is no more expensive than using lambda expressions, and continue to prove that over time, that's great - but it sounds like you're keener on assuming that's the case, which I definitely wouldn't.

查看更多
兄弟一词,经得起流年.
4楼-- · 2019-03-27 12:06

Raffi lets look at an example on how the compiler inlining you are talking about will change the program logic and compiler needs to be very smart enough to be able to figure that out:

   public String process(){
        //do some important bussiness logic
        return "Done processing";
    }

1) Without inlining the process() will be callled regardless of logging level:

log( Level.FINE, "Very expensive string to construct ..." + process() );

2) With inlining the process() will be called only under certain logging level and our important bussiness logic wont be able to run:

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

The compiler in this case has to figure out how the message string is created and not inline the method if it calls any other method during its creation.

查看更多
登录 后发表回答