Fodder Fodder - 2 months ago 8
Java Question

Why can a method that takes varargs be optimised into a series of monomorphic calls only if it is static?

At vJUG24, one of the topics was JVM performance.

Slides can be found here.

He had an example:

static void log(Object... args) {
for(Object arg : args) {
System.out.println(arg);
}
}


which was called via (can't quite read the slide properly, but it's similar):

void doSomething() {
log("foo", 4, new Object());
}


He said because it was a static method, it could be optimised by inlining it like this:

void doSomething() {
System.out.println("foo");
System.out.println(new Integer(4).toString());
System.out.println(new Object().toString());
}


Why is it important that the log method is static for the JVM to make this optimisation?

Answer

Because a non-static method can be overridden. At compile time, you don't know instances of which class will be used, thus you don't know which method to inline. Especially if all you know about the 'provider' of that method is an interface.

Here's an example:

interface Logger {
   public void log(Object... args);
}

class SysOutLogger
implements Logger {
  public void log(Object... args) {
    for(Object arg : args) {
        System.out.println(arg);
    }
}

class SysErrLogger
implements Logger {
  static void log(Object... args) {
    for(Object arg : args) {
        System.err.println(arg);
    }
  }
}

class Processor {
  protected Logger logProvider;


  public void setLogProvider(Logger logger) {
    this.logProvider=logger;
  }

  public void doSomething() {
    // What do you want me to inline *at compile time* here???
    // At best, changes to make this more performant may happen
    // at run-time, using JiT-compilation
    this.logProvider("foo", 4, new Object());
  }
}

BTW, the above happens irrespective of the presence of VarArgs.

The VarArgs complicate the problem for static methods if the compiler is required to unwind cycles with VarArgs. In the provided example, how is the compiler to react with a call like this:

void doSomething() {
  // now, *inline this and unwind the cycle* - 
  // is the generated small enough to justify inlining?
  log(
    "Just started to do something",
    "doSomething", // name of the function
    new Date(), // log time
    testSuiteNumber,
    testCaseNumber,
    executionOS,
    configuration.getUserName(),
    // ... add some more here
  );

  // does something

  // now, inline this and unwind the cycle - is the generated code better?
  log(
    "Just finished doing something",
    "doSomething", // name of the function
    new Date(), // log time
    testSuiteNumber,
    testCaseNumber,
    executionOS,
    configuration.getUserName(),
    // ... add some more here
  );
}