Будет ли Java встроенный метод (ы) во время оптимизации? - PullRequest
27 голосов
/ 14 октября 2011

Интересно, достаточно ли умен JVM / javac, чтобы превратить

// This line...
string a = foo();

string foo()
{
  return bar();
}

string bar()
{
  return some-complicated-string computation;
}

в

string a = bar();

или удалить ненужный вызов foo () в случае выпуска (из-за недоступного кода):

string a = foo(bar());

// bar is the same
...

string foo(string b)
{
  if (debug) do-something-with(b);
}

Я чувствую, что да для первого примера и "не очень уверен" для второго, но кто-нибудь может дать мне несколько указателей / ссылок, чтобы подтвердить это?

Ответы [ 6 ]

29 голосов
/ 15 октября 2011

javac представит байт-код, который является точным представлением исходной Java-программы, сгенерировавшей байт-код (за исключением определенных ситуаций, когда он может оптимизировать: постоянное свертывание и устранение мертвого кода ).Однако оптимизация может выполняться JVM, когда она использует JIT-компилятор.

Для первого сценария выглядит, что JVM поддерживает встраивание (см. Методы здесь и посмотрите здесь для примера встраивания в JVM.

Я не смог найти никаких примеров встраивания метода, выполняемого самой javac.Я попытался скомпилировать несколько примеров программ (похожих на ту, что вы описали в своем вопросе), и ни одна из них, похоже, не содержала прямого метода, даже когда он был final.Казалось бы, такого рода оптимизации выполняются JIT-компилятором JVM, а не javac.«Компилятор», упомянутый в Methods здесь , кажется JIT-компилятором HotSpot JVM, а не javac.

Из того, что я вижу, javac поддерживает устранение мертвого кода (см. Пример для второго случая) и постоянное свертывание .При свертывании констант компилятор будет предварительно вычислять выражения констант и использовать вычисленное значение вместо выполнения вычислений во время выполнения.Например:

public class ConstantFolding {

   private static final int a = 100;
   private static final int b = 200;

   public final void baz() {
      int c = a + b;
   }
}

компилируется в следующий байт-код:

Compiled from "ConstantFolding.java"
public class ConstantFolding extends java.lang.Object{
private static final int a;

private static final int b;

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

public final void baz();
  Code:
   0:   sipush  300
   3:   istore_1
   4:   return

}

Обратите внимание, что байт-код имеет sipush 300 вместо aload '* getfield s и iadd.300 - расчетное значение.Это также относится и к private final переменным.Если a и b не были статическими, результирующий байт-код будет иметь вид:

Compiled from "ConstantFolding.java"
public class ConstantFolding extends java.lang.Object{
private final int a;

private final int b;

public ConstantFolding();
  Code:
   0:   aload_0
   1:   invokespecial   #1; //Method java/lang/Object."<init>":()V
   4:   aload_0
   5:   bipush  100
   7:   putfield    #2; //Field a:I
   10:  aload_0
   11:  sipush  200
   14:  putfield    #3; //Field b:I
   17:  return

public final void baz();
  Code:
   0:   sipush  300
   3:   istore_1
   4:   return

}

Здесь также используется sipush 300.

Для второго случая (dead-Исключение кода), я использовал следующую тестовую программу:

public class InlineTest {

   private static final boolean debug = false;

   private void baz() {
      if(debug) {
         String a = foo();
      }
   }

   private String foo() {
      return bar();
   }

   private String bar() {
      return "abc";
   }
}

, которая дает следующий байт-код:

Compiled from "InlineTest.java"
public class InlineTest extends java.lang.Object{
private static final boolean debug;

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

private void baz();
  Code:
   0:   return

private java.lang.String foo();
  Code:
   0:   aload_0
   1:   invokespecial   #2; //Method bar:()Ljava/lang/String;
   4:   areturn

private java.lang.String bar();
  Code:
   0:   ldc #3; //String abc
   2:   areturn

}

Как видите, foo вообще не вызывается вbaz потому что код внутри блока if фактически "мертв".

В Sun (сейчас Oracle) HotSpot JVM объединяет интерпретацию байт-кода и компиляцию JIT.Когда байт-код представляется в JVM, код первоначально интерпретируется, но JVM будет контролировать байт-код и выбирать части, которые часто выполняются.Он включает эти части в собственный код, чтобы они работали быстрееДля части байт-кода, которая не используется так часто, эта компиляция не выполнена.Это также хорошо, потому что компиляция имеет некоторые издержкиТак что это действительно вопрос компромисса.Если вы решите скомпилировать весь байт-код в собственный код, тогда у кода может быть очень большая задержка запуска.

В дополнение к мониторингу байт-кода JVM также может выполнять статический анализ байт-кода во время его интерпретации.и загрузите его для дальнейшей оптимизации.

Если вы хотите знать конкретные виды оптимизации, которые выполняет JVM, эта страница в Oracle очень полезна.Он описывает методы производительности, используемые в JSM HotSpot.

3 голосов
/ 15 октября 2011

JVM, скорее всего, будет встроенным. В общем, лучше оптимизировать для удобства чтения. Пусть JVM выполнит оптимизацию среды выполнения.

Эксперт JVM Брайан Гетц говорит, что final не влияет на методы, которые будут встроены.

2 голосов
/ 15 октября 2011

«Высокооптимизирующий» JIT-компилятор встроит оба случая (и, @Mysticial, он может даже встроить некоторые полиморфные случаи, используя различные формы обмана).

Вы можете увеличить шансы на встраивание,создание методов final и несколько других приемов.

javac делает несколько примитивных вставок, в основном из final / private методов, в первую очередь предназначенных для помощи в некоторых парадигмах условной компиляции.

2 голосов
/ 15 октября 2011

в том же файле класса javac сможет встроить static и final (другие файлы класса могут изменить встроенную функцию)

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

0 голосов
/ 15 октября 2011

Я могу ошибаться, но я чувствую, что "нет во всех случаях".Потому что ваш string bar() может быть переопределен перегруженными другими классами в том же пакете.final методы являются хорошими кандидатами, но это зависит от JIT.

Еще одна интересная заметка: здесь .

0 голосов
/ 15 октября 2011

Если вы сгенерировали исключение в bar () и распечатали трассировку стека, вы увидите весь путь вызовов ... Я думаю, что Java соблюдает все из них.

Второй случай такой же, отладка - это просто переменная вашей системы, а не определение, как в C ++, поэтому ее обязательно нужно предварительно оценить.

...