Подтвердить что ты не робот

Java: Как JVM оптимизирует вызов пустой и пустой функции?

Предположим, что у нас есть следующие классы:

public class Message extends Object {}

public class Logger implements ILogger {
 public void log(Message m) {/*empty*/}
}

и следующую программу:

public static void main(String args[]) {
  ILogger l = new Logger();
  l.log((Message)null); // a)
  l.log(new Message()); // b)
}

Будет ли компилятор Java выделять операторы a и b? В обоих случаях (зачистка или не удаление), что является обоснованием решения Java-компилятора?

4b9b3361

Ответ 1

Будет ли компилятор Java отключить инструкции a и b?

Компилятор javac (источник для байт-кода) не будет блокировать вызов. (Это легко проверить, изучив байт-коды, например, глядя на вывод javap -c.)

В обоих случаях (удаление или удаление), что является обоснованием решения компилятора Java?

Соответствие с JLS:-).

С прагматической точки зрения:

  • Если компилятор javac оптимизировал вызовы, отладчик Java не смог бы их вообще увидеть... что было бы довольно запутанным для разработчика.
  • Ранняя оптимизация (через javac) приведет к поломке, если класс Message и основной класс были скомпилированы/изменены независимо. Например, рассмотрим следующую последовательность:

    • Message скомпилирован,
    • основной класс скомпилирован,
    • Message редактируется так, что log выполняет что-то... и перекомпилируется.

    Теперь у нас есть неправильно скомпилированный основной класс, который не делает правильные вещи в a и b, потому что преждевременно встроенный код устарел.


Однако компилятор JIT может оптимизировать код во время выполнения различными способами. Например:

  • Вызов метода a и b может быть встроен, если компилятор JIT может вывести, что диспетчеризация виртуального метода не требуется. (Если Logger является единственным классом, используемым приложением, реализующим ILogger, это не проблема для хорошего компилятора JIT.)

  • После вставки первого вызова метода компилятор JIT может определить, что тело является noop и оптимизирует вызов.

  • В случае вызова второго метода JIT-компилятор может дополнительно вывести (путем анализа escape-кода), что объект Message не должен выделяться в куче... или вообще вообще.

(Если вы хотите знать, что на самом деле делает компилятор JIT (на вашей платформе), JVM Hotspot имеют параметр JVM, который выгружает встроенный код JIT для выбранных методов.)

Ответ 2

Разборка следующего файла (с помощью javap -c) предполагает, что при компиляции до байт-кода не выделяется компилятором 1.7.0:

public class Program
{
    public static class Message extends Object {}

    public interface ILogger {
        void log(Message m);
    }

    public static class Logger implements ILogger {
        public void log(Message m) { /* empty */ }
    }

    public static void main(String[] args) {
        ILogger l = new Logger();
        l.log((Message)null); // a)
        l.log(new Message()); // b)
    }
}

Результат ниже. Ключевыми битами являются вызовы на строках 13 и 26.

Compiled from "Program.java"
public class Program {
  public Program();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: new           #2                  // class Program$Logger
       3: dup
       4: invokespecial #3                  // Method Program$Logger."<init>":()V
       7: astore_1
       8: aload_1
       9: aconst_null
      10: checkcast     #4                  // class Program$Message
      13: invokeinterface #5,  2            // InterfaceMethod Program$ILogger.log:(LProgram$Message;)V
      18: aload_1
      19: new           #4                  // class Program$Message
      22: dup
      23: invokespecial #6                  // Method Program$Message."<init>":()V
      26: invokeinterface #5,  2            // InterfaceMethod Program$ILogger.log:(LProgram$Message;)V
      31: return
}

EDIT: Однако, как отметил @mikera, вероятно, что компилятор JIT будет делать дальнейшие оптимизации при запуске программы, что может устранить вызовы. Я не знаю достаточно о деталях, чтобы комментировать это, к сожалению.

SIDE ПРИМЕЧАНИЕ. Вас может заинтересовать эта ссылка, которая касается методов производительности, используемых JVM Hotspot:

https://wikis.oracle.com/display/HotSpotInternals/PerformanceTechniques

Ответ 3

Вероятно, в конце концов, определенно не сразу, а не обязательно. JIT не дает никаких гарантий, особенно не для метода, который называется только пару раз. (Вероятно, это будет классифицировано как простое вложение вызова log, а встроенный код будет... ничего.)

Ответ 4

невозможно сказать окончательно - это будет зависеть от реализации компилятора JVM/Java.

Умный достаточно компилятор может доказать, что ни одно из утверждений не имеет эффекта, и поэтому может их устранить. Я считаю, что большинство современных JVM будут делать это, хотя вам нужно будет проверить свою конкретную конфигурацию, чтобы быть уверенным.

a) легче оптимизировать, чем b), поскольку b) включает вызов конструктора, который компилятор также должен доказать, не имеет побочных эффектов, прежде чем он сможет оптимизировать весь оператор.

Обратите внимание, что вы ожидаете, что такое устранение будет выполнено компилятором JIT, а не компилятором Java, то есть, вероятно, будет генерироваться байт-код, включающий вызовы функций журнала, но это позже оптимизируется компилятором JIT когда он компилируется до собственного кода.

Кроме того, поскольку JIT может перекомпилировать статистику времени и т.д., возможно, что код будет там, чтобы начать с него, но скомпилировать позже после последовательных оптимизаций.

Ответ 5

Я не думаю, что java-компилятор удалит вызов, потому что вызываемый метод пуст, потому что вы можете изменить метод в будущем и не вносить никаких изменений в основной метод.

Ответ 6

Сервер JIT, безусловно, будет встроен и в конечном итоге полностью исключит код, если вы сделаете ссылку final: final ILogger l = new Logger(); В современной JVM большая часть оптимизации выполняется JIT.