Оптимизирует ли компилятор Java ненужный тернарный оператор?

Я просматривал код, в котором некоторые программисты использовали избыточные тернарные операторы «для удобочитаемости». Такие как:

boolean val = (foo == bar && foo1 != bar) ? true : false;

Очевидно, было бы лучше просто присвоить результат оператора переменной boolean, но разве компилятору это важно?


person Bakna    schedule 02.02.2019    source источник
comment
Из любопытства, эти же кодеры делают что-то вроде if( foo == true ) для удобочитаемости?   -  person Roddy of the Frozen Peas    schedule 02.02.2019
comment
Это может быть или не быть частью оригинальной тройки, которая вызвала этот вопрос... но это выходит за рамки этого вопроса. :)   -  person Bakna    schedule 02.02.2019
comment
Мне жаль. Мне тоже приходится работать с кодерами, которые этим занимаются. По моему опыту, вы очень быстро теряете всякую веру. ;)   -  person Roddy of the Frozen Peas    schedule 02.02.2019
comment
На самом деле нет другого способа узнать, кроме как попробовать его с помощью декомпилятора java или средства просмотра байт-кода, и даже тогда это, вероятно, будет зависеть от версии компилятора. Я имею в виду, предположим, что кто-то появится и скажет, что этого не будет, почему вы должны им верить? Я предполагаю, что так и будет, а если нет, то JIT, скорее всего, сделает это, но поверьте мне на слово с долей скептицизма.   -  person Mike Nakis    schedule 02.02.2019
comment
Они делают это со всеми булевыми выражениями? Это довольно странно. Мне было бы любопытно, делают ли они такие вещи, как if ( isValid(input) ? true : false ).   -  person Paul    schedule 02.02.2019
comment
Я бы меньше беспокоился о компиляторе и больше о стандартах кодирования и обзорах кода. Выражения никогда* не станут вашими горячими точками при профилировании, поэтому моя главная забота будет заключаться в том, чтобы такие выражения на самом деле менее читались, а не в том, что они могли бы замедлить программу на одну или две инструкции.   -  person dlev    schedule 03.02.2019
comment
Это более читабельно, только если вы некомпетентны в булевой логике. ...Может быть, даже не тогда.   -  person jpmc26    schedule 03.02.2019
comment
@Paulpro Я подозреваю, что это больше связано с тем, чтобы код читался как текст: int x = y читается естественно, если установить целое число x в y, но bool z = x == y читается как установить логическое значение z на x равно y, что звучит забавно.   -  person Challenger5    schedule 03.02.2019
comment
Не существует такой вещи, как компилятор Java. Чтобы на ваш вопрос можно было ответить, вам нужно указать, о каком компиляторе Java (и, в идеале, о какой именно версии) вы говорите. Вы про ЕКЖ? GCJ? Шутки? JScp?   -  person Jörg W Mittag    schedule 03.02.2019
comment
Бьюсь об заклад, они пишут if (foo == false), потому что думают, что ! трудно различить в !foo...   -  person Bakuriu    schedule 03.02.2019
comment
@Paulpro if ( (isValid(input) ? true : false) == true )   -  person JJJ    schedule 03.02.2019


Ответы (6)


Я считаю, что ненужное использование тернарного оператора делает код более запутанным и менее читаемым, вопреки первоначальному замыслу.

При этом поведение компилятора в этом отношении можно легко проверить, сравнив байт-код, скомпилированный JVM.
Вот два фиктивных класса, чтобы проиллюстрировать это:

Случай I (без тернарного оператора):

class Class {

    public static void foo(int a, int b, int c) {
        boolean val = (a == c && b != c);
        System.out.println(val);
    }

    public static void main(String[] args) {
       foo(1,2,3);
    }
}

Случай II (с тернарным оператором):

class Class {

    public static void foo(int a, int b, int c) {
        boolean val = (a == c && b != c) ? true : false;
        System.out.println(val);
    }

    public static void main(String[] args) {
       foo(1,2,3);
    }
}

Байт-код для метода foo() в случае I:

       0: iload_0
       1: iload_2
       2: if_icmpne     14
       5: iload_1
       6: iload_2
       7: if_icmpeq     14
      10: iconst_1
      11: goto          15
      14: iconst_0
      15: istore_3
      16: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
      19: iload_3
      20: invokevirtual #3                  // Method java/io/PrintStream.println:(Z)V
      23: return

Байт-код для метода foo() в случае II:

       0: iload_0
       1: iload_2
       2: if_icmpne     14
       5: iload_1
       6: iload_2
       7: if_icmpeq     14
      10: iconst_1
      11: goto          15
      14: iconst_0
      15: istore_3
      16: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
      19: iload_3
      20: invokevirtual #3                  // Method java/io/PrintStream.println:(Z)V
      23: return

Обратите внимание, что в обоих случаях байт-код идентичен, т.е. компилятор игнорирует тернарный оператор при компиляции значения логического значения val.


ИЗМЕНИТЬ:

Разговор по этому вопросу пошел по одному из нескольких направлений.
Как показано выше, в обоих случаях (с избыточной тройкой или без нее) скомпилированный байт-код java идентичен.
Может ли компилятор Java рассматривать это как оптимизацию, зависит от вашего определения оптимизации. В некоторых отношениях, как неоднократно указывалось в других ответах, имеет смысл утверждать, что нет - это не оптимизация, а тот факт, что в обоих случаях сгенерированный байт-код представляет собой простейший набор операций стека, который выполняет эта задача, независимо от троичной.

Однако по основному вопросу:

Очевидно, было бы лучше просто присвоить результат оператора логической переменной, но разве это важно для компилятора?

Простой ответ - нет. Компилятору все равно.

person yuvgin    schedule 02.02.2019
comment
+1, потому что я считаю, что ненужное использование тернарного оператора делает код более запутанным и менее читаемым, вопреки первоначальному замыслу. Я бы переместил это в начало, если бы я был на вашем месте. - person jpmc26; 03.02.2019
comment
Под удобочитаемостью авторы, возможно, имели в виду понятность для абсолютных нубов, что может быть правильным моментом. if(myBoolean) несколько сбивает с толку новичков, тогда как if(myBoolean == true) прямолинеен. - person RaminS; 03.02.2019
comment
Вы никогда не должны делать вывод о поведении языка из примера компилятора. Возможно, оптимизация необязательна и вы можете получить разные результаты на разных компиляторах. Принимая эти решения, всегда обращайтесь к спецификации языка. - person cwm9; 06.12.2020

Вопреки ответам Павла Хорала, Codo и yuvgin Я утверждаю, что компилятор НЕ оптимизирует (или игнорирует) тернарный оператор. (Пояснение: я имею в виду компилятор Java в байт-код, а не JIT)

Смотрите тестовые примеры.

Класс 1: оценивает логическое выражение, сохраняет его в переменной и возвращает эту переменную.

public static boolean testCompiler(final int a, final int b)
{
    final boolean c = ...;
    return c;
}

Итак, для разных булевых выражений проверяем байт-код: 1. Выражение: a == b

Байт-код

   0: iload_0
   1: iload_1
   2: if_icmpne     9
   5: iconst_1
   6: goto          10
   9: iconst_0
  10: istore_2
  11: iload_2
  12: ireturn
  1. Выражение: a == b ? true : false

Байт-код

   0: iload_0
   1: iload_1
   2: if_icmpne     9
   5: iconst_1
   6: goto          10
   9: iconst_0
  10: istore_2
  11: iload_2
  12: ireturn
  1. Выражение: a == b ? false : true

Байт-код

   0: iload_0
   1: iload_1
   2: if_icmpne     9
   5: iconst_0
   6: goto          10
   9: iconst_1
  10: istore_2
  11: iload_2
  12: ireturn

Случаи (1) и (2) компилируются в один и тот же байт-код не потому, что компилятор оптимизирует тернарный оператор, а потому, что ему необходимо каждый раз выполнять этот тривиальный тернарный оператор. На уровне байт-кода необходимо указать, следует ли возвращать true или false. Чтобы убедиться в этом, посмотрите на случай (3). Это точно такой же байт-код, за исключением строк 5 и 9, которые поменялись местами.

Что происходит тогда и a == b ? true : false при декомпиляции дает a == b? Это выбор декомпилятора, который выбирает самый простой путь.

Кроме того, на основе эксперимента «Класс 1» разумно предположить, что a == b ? true : false точно такое же, как a == b в том, как оно транслируется в байт-код. Однако это не так. Чтобы проверить это, мы исследуем следующий «Класс 2», единственная разница с «Классом 1» заключается в том, что он не сохраняет логический результат в переменной, а вместо этого немедленно возвращает его.

Класс 2: оценка логического выражения и возврат результата (без сохранения его в переменной).

public static boolean testCompiler(final int a, final int b)
{
    return ...;
}
    1. a == b

Байт-код:

   0: iload_0
   1: iload_1
   2: if_icmpne     7
   5: iconst_1
   6: ireturn
   7: iconst_0
   8: ireturn
    1. a == b ? true : false

Байт-код

   0: iload_0
   1: iload_1
   2: if_icmpne     9
   5: iconst_1
   6: goto          10
   9: iconst_0
  10: ireturn
    1. a == b ? false : true

Байт-код

   0: iload_0
   1: iload_1
   2: if_icmpne     9
   5: iconst_0
   6: goto          10
   9: iconst_1
  10: ireturn

Здесь очевидно, что a == b и a == b ? true : false выражения компилируются по-разному, так как случаи (1) и (2) дают разные байт-коды (случаи (2) и (3), как и ожидалось, поменялись местами только их строки 5,9).

Сначала это меня удивило, так как я ожидал, что все 3 случая будут одинаковыми (за исключением переставленных строк 5,9 случая (3)). Когда компилятор встречает a == b, он оценивает выражение и сразу после него возвращается, в отличие от встречи с a == b ? true : false, где он использует goto для перехода к строке ireturn. Я понимаю, что это сделано для того, чтобы оставить место для оценки потенциальных утверждений внутри «истинного» случая тернарного оператора: между проверкой if_icmpne и строкой goto. Даже если в данном случае это просто логическое значение true, компилятор обрабатывает его так же, как в общем случае, когда присутствует более сложный блок.
С другой стороны, "класс 1" скрыл этот факт, так как в ветке true были также istore, iload и не только ireturn, форсирующие команду goto и приводившие к точно такому же байт-коду в случаях (1) и (2).

В качестве примечания относительно тестовой среды: эти байт-коды были созданы с помощью последней версии Eclipse (4.10), в которой используется соответствующий компилятор ECJ, отличный от javac, который использует IntelliJ IDEA.

Однако, читая созданный javac байт-код в других ответах (которые используют IntelliJ), я считаю, что та же логика применима и там, по крайней мере, для эксперимента «Класс 1», где значение было сохранено и не возвращено немедленно.

Наконец, как уже указывалось в других ответах (например, от supercat и jcsahnwaldt) , как в этой теме, так и в других вопросах SO тяжелая оптимизация выполняется компилятором JIT, а не компилятором java-->java-bytecode, поэтому эти проверки, хотя и информативные для перевода байт-кода, не являются хорошей мерой того, как будет выполняться окончательный оптимизированный код.

Дополнение: ответ jcsahnwaldt сравнивает созданный javac и ECJ байт-код для аналогичных случаев.

(В качестве отказа от ответственности: я не изучал компиляцию или дизассемблирование Java так много, чтобы на самом деле знать, что он делает под капотом; мои выводы в основном основаны на результатах приведенных выше экспериментов.)

person tryman    schedule 02.02.2019
comment
Случай 3 явно претерпел некоторую оптимизацию в компиляторе: в стеке JVM никогда не бывает значения, представляющего значение выражения a == b; он был оптимизирован. Тот факт, что такие оптимизации обычно происходят всякий раз, когда результат логического оператора используется в качестве условия, не мешает этому быть оптимизацией. И та же самая оптимизация работает в случае 2. - person hmakholm left over Monica; 03.02.2019
comment
Если бы оптимизации не было, код сначала создал бы логическое значение a == b в стеке (включая условный переход и две инструкции с постоянными загрузкой), а затем был бы еще один условный переход на результирующее значение в другие инструкции загрузки-константы, представляющие две ветви тернарного оператора. - person hmakholm left over Monica; 03.02.2019
comment
Концепция оценки условного перехода и перехода к любому из двух мест на основе того, является ли он истинным или ложным, может быть оценена напрямую, без разложения его на оценку условного перехода для получения bool, а затем перехода на основе этого. - person supercat; 03.02.2019
comment
@supercat: Да, может. Это пример оптимизации, которую компилятор байт-кода выполняет. - person hmakholm left over Monica; 03.02.2019
comment
@HenningMakholm: у компилятора может быть отдельная логика для оценки выражения bool, которое возникает в контексте, где он управляет ветвью, а не там, где он генерирует значение. Спецификация языка не должна различать контексты с точки зрения пользователя, но для целей генерации кода это тривиально и на самом деле не является оптимизацией. - person supercat; 03.02.2019
comment
@supercat: вы описываете простую оптимизацию компилятора, которую выполняет компилятор байт-кода. Это хорошая оптимизация, которую стоит выполнить в компиляторе. Я не понимаю, почему вы продолжаете утверждать, что это не оптимизация. Это семантически правильно и приводит к лучшему коду, чем наивный перевод. Какие еще у вас есть критерии для использования слова «оптимизация»? - person hmakholm left over Monica; 03.02.2019
comment
Точно. a == b ? true : false является a == b. Буквально они означают одно и то же, и в первом случае не нужно выполнять никаких дополнительных операций. Это не оптимизация, это просто случай, когда два полностью эквивалентных оператора создают один и тот же переведенный код, что не должно вызывать удивления. Java-код — это описание программы; это абстракция. По умолчанию это не однозначное сопоставление английских слов с инструкциями ЦП или JVM. Здесь нет логического ответвления, поэтому нет и ожидания практического. - person Lightness Races in Orbit; 03.02.2019
comment
@LightnessRacesinOrbit: То, что оптимизация создает код с правильной семантикой, не означает, что это не оптимизация. Наоборот, концепции оптимизации присуще то, что она создает эквивалентный код. В противном случае это была бы не оптимизация, а ошибка компилятора. - person hmakholm left over Monica; 03.02.2019
comment
@HenningMakholm Я знаю об этом; это не то, что я говорил. Я хочу сказать, что не каждая часть процесса перевода исходного кода в реальную программу является оптимизацией, и я думаю, что вы провели грань между обычным процессом перевода и оптимизацией не в том месте. Оптимизация создает логику, которая, хотя и имеет ту же семантику, заметно отличается от исходного кода по своему подходу. Это не так - это буквально идентично. Хотя на самом деле это просто, по иронии судьбы, спор о семантике. - person Lightness Races in Orbit; 03.02.2019
comment
@LightnessRacesinOrbit: Конечно, не каждая часть перевода является оптимизацией. Но все, что происходит во время перевода, и приводит к лучшему коду, чем наивный перевод, по определению является оптимизацией. - person hmakholm left over Monica; 03.02.2019
comment
@HenningMakholm Наивный перевод это то, что мы получаем. Вот что я говорю. Нет более наивного перевода, по крайней мере, если он не был на самом деле надуманным (например, ввод ветвей там, где логическая ветвь никогда не вызывалась! компилятору пришлось бы преднамеренно вводить это Дополнительная работа) - person Lightness Races in Orbit; 03.02.2019
comment
@LightnessRacesinOrbit: Нет, то, что здесь показано, не является наивным переводом. Наивный перевод переводил бы каждое выражение типа boolean в код, который создает логическое значение в стеке, независимо от контекста. Делать что-то отличное от родного перевода по определению является оптимизацией (если только это не ошибка). - person hmakholm left over Monica; 03.02.2019
comment
@HenningMakholm: В Java что-то вроде while (condition) statement1; эффективно обрабатывается как label1: conditionalBranch(condition, label2, label3); label2: statement1; goto label1; label3:. Обработка conditionalBranch обрабатывает определенные операторы, вызывая conditionalBranch для операндов, например. если `cond1`` является константой true, эта операция станет безусловной ветвью к label2; если константа false, эта операция будет заменена безусловным переходом к label3. - person supercat; 03.02.2019
comment
Если condition равен cond1 ? cond2 : cond3;, он будет оцениваться путем оценки cond1 и перехода либо к функции, которая разветвляется на основе cond2, либо к той, которая разветвляется на основе cond3. Стандарт требует, чтобы реализации выдавали сигнал, если тело while() недоступно, и хотя можно было бы сделать такие определения при оценке конструкций ?: на основе значений, проще оценить ?: в условном выражении как условное выполнение одного из двух двусторонние ответвления. - person supercat; 03.02.2019
comment
@supercat: Спецификация языка требует, чтобы для ошибки недостижимого кода использовался очень точно определенный алгоритм распространения констант. Ничего умнее ни глупее для этой цели не допускается. Это не очень хорошая идея, основанная на фактическом коде, который вы генерируете; это заблокировало бы слишком много небольших оптимизаций генерации кода, которые вы, возможно, захотите сделать на этом этапе. - person hmakholm left over Monica; 03.02.2019
comment
РЕДАКТИРОВАТЬ: теперь я вижу, что редактирование не было сделано ОП. Я предлагаю отредактировать себя. Если это будет принято, я с удовольствием отменю DV. Я никоим образом не утверждал, что это была оптимизация компилятора, я скорее сравнил байт-код, чтобы показать, что компилятор игнорирует избыточную тройную операцию. Что касается этого ответа, я думаю, что он делает несколько очень убедительных выводов. - person yuvgin; 03.02.2019
comment
@yuvgin Нет, это не так, хотя при первоначальной публикации ответа я также имел в виду ваш ответ в категории оптимизации. Я не уверен, что понимаю разницу: если компилятор игнорирует избыточный тернарный оператор, разве это не оптимизация? В любом случае, мы все равно приходим к разным выводам. Я понимаю, это не игнорирует избыточный тернарный оператор. [1/2, слишком длинный комментарий] - person tryman; 03.02.2019
comment
Я понял это из эксперимента класса 2, где при немедленном возврате значения a == b и a == b ? true : false генерируют разные байт-коды. Класс 1, из-за того, что сначала сохраняется переменная, а не сразу возвращается, кажется, просто получается один и тот же байт-код в обоих случаях (что тоже было неожиданностью для меня, это странное восклицание в моем исходном ответе). Я отредактирую выводы класса 2, так как они кажутся неясными. Я с радостью приму правку, отражающую это. [2/2, слишком длинный комментарий] - person tryman; 03.02.2019
comment
@tryman: Учитывая что-то вроде if (cond1 ? cond2 : cond3) X else Y;, cond1 выбирает между двумя действиями ветвления: перейти к X или Y на основе cond2 или перейти к X или Y на основе cond3. Условный переход на основе константы — это, конечно, простой переход. Я предполагаю, что замена ветки на безусловную ветку на ветку к цели может быть оптимизацией, но это тривиально. Учитывая bool1 = bool2 ? true : false;, bool2 выбирает между двумя действиями, создающими ценность: загрузить true или загрузить false. - person supercat; 03.02.2019
comment
Я полагаю, что более интересным экспериментом может быть что-то вроде while (a==b ? false : false) или someBool = (a==b) ? false:false;. У меня нет под рукой javac, но эти случаи были бы интересны, поскольку тело цикла было бы статически недостижимо, но я не думаю, что cond ? const1 : const1 считается постоянным выражением. - person supercat; 03.02.2019

Да, компилятор Java оптимизирует. Это легко проверить:

public class Main1 {
  public static boolean test(int foo, int bar, int baz) {
    return foo == bar && bar == baz ? true : false;
  }
}

После javac Main1.java и javap -c Main1:

  public static boolean test(int, int, int);
    Code:
       0: iload_0
       1: iload_1
       2: if_icmpne     14
       5: iload_1
       6: iload_2
       7: if_icmpne     14
      10: iconst_1
      11: goto          15
      14: iconst_0
      15: ireturn

public class Main2 {
  public static boolean test(int foo, int bar, int baz) {
    return foo == bar && bar == baz;
  }
}

После javac Main2.java и javap -c Main2:

  public static boolean test(int, int, int);
    Code:
       0: iload_0
       1: iload_1
       2: if_icmpne     14
       5: iload_1
       6: iload_2
       7: if_icmpne     14
      10: iconst_1
      11: goto          15
      14: iconst_0
      15: ireturn

Оба примера заканчиваются одним и тем же байт-кодом.

person Pavel Horal    schedule 02.02.2019

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

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

Изучение байт-кода, выводимого компилятором javac, не является хорошим способом судить о том, будет ли конструкция выполняться эффективно или неэффективно. Вполне вероятно, что может быть какая-то реализация JVM, в которой такие конструкции, как (someCondition ? true : false), будут работать хуже, чем (someCondition), а в некоторых случаях они будут работать одинаково.

person supercat    schedule 02.02.2019
comment
Изучение вывода байт-кода показывает, что компилятор выполняет определенные оптимизации. Он не выполняет многих видов оптимизации, но базовая оптимизация представления значения выражения boolean либо в виде потока управления, либо в виде значения в стеке (и генерации кода для преобразования между представлениями если выражение хочет создать представление, отличное от того, что хочет потреблять контекст) выполняется компилятором байт-кода. (Да, это оптимизация; простейший правильный перевод с Java на байт-код не поможет). - person hmakholm left over Monica; 03.02.2019
comment
@supercat Я считаю, что этот вопрос актуален и в целом поддерживает ваш случай: Оптимизация компилятором java ? - person tryman; 03.02.2019

В IntelliJ я скомпилировал ваш код и открыл файл класса, который автоматически декомпилируется. Результат:

boolean val = foo == bar && foo1 != bar;

Так что да, компилятор Java оптимизирует его.

person Codo    schedule 02.02.2019
comment
Я не думаю, что это точный способ проверить это. Декомпилятор может внести некоторые коррективы в вывод, чтобы его было легче читать. Разные декомпиляторы тоже могут давать разные результаты. Имеет смысл сравнить сам фактический байт-код. - person Burak; 05.02.2019

Я хотел бы обобщить прекрасную информацию, представленную в предыдущих ответах.

Давайте посмотрим, что делают javac Oracle и ecj Eclipse со следующим кодом:

boolean  valReturn(int a, int b) { return a == b; }
boolean condReturn(int a, int b) { return a == b ? true : false; }
boolean   ifReturn(int a, int b) { if (a == b) return true; else return false; }

void  valVar(int a, int b) { boolean c = a == b; }
void condVar(int a, int b) { boolean c = a == b ? true : false; }
void   ifVar(int a, int b) { boolean c; if (a == b) c = true; else c = false; }

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

Я скомпилировал код с помощью javac и ecj, а затем декомпилировал его с помощью Oracle javap.

Вот результат для javac (я пробовал javac 9.0.4 и 11.0.2 - они генерируют точно такой же код):

boolean valReturn(int, int);
  Code:
     0: iload_1
     1: iload_2
     2: if_icmpne     9
     5: iconst_1
     6: goto          10
     9: iconst_0
    10: ireturn

boolean condReturn(int, int);
  Code:
     0: iload_1
     1: iload_2
     2: if_icmpne     9
     5: iconst_1
     6: goto          10
     9: iconst_0
    10: ireturn

boolean ifReturn(int, int);
  Code:
     0: iload_1
     1: iload_2
     2: if_icmpne     7
     5: iconst_1
     6: ireturn
     7: iconst_0
     8: ireturn

void valVar(int, int);
  Code:
     0: iload_1
     1: iload_2
     2: if_icmpne     9
     5: iconst_1
     6: goto          10
     9: iconst_0
    10: istore_3
    11: return

void condVar(int, int);
  Code:
     0: iload_1
     1: iload_2
     2: if_icmpne     9
     5: iconst_1
     6: goto          10
     9: iconst_0
    10: istore_3
    11: return

void ifVar(int, int);
  Code:
     0: iload_1
     1: iload_2
     2: if_icmpne     10
     5: iconst_1
     6: istore_3
     7: goto          12
    10: iconst_0
    11: istore_3
    12: return

А вот результат для ecj (версия 3.16.0):

boolean valReturn(int, int);
  Code:
     0: iload_1
     1: iload_2
     2: if_icmpne     7
     5: iconst_1
     6: ireturn
     7: iconst_0
     8: ireturn

boolean condReturn(int, int);
  Code:
     0: iload_1
     1: iload_2
     2: if_icmpne     9
     5: iconst_1
     6: goto          10
     9: iconst_0
    10: ireturn

boolean ifReturn(int, int);
  Code:
     0: iload_1
     1: iload_2
     2: if_icmpne     7
     5: iconst_1
     6: ireturn
     7: iconst_0
     8: ireturn

void valVar(int, int);
  Code:
     0: iload_1
     1: iload_2
     2: if_icmpne     9
     5: iconst_1
     6: goto          10
     9: iconst_0
    10: istore_3
    11: return

void condVar(int, int);
  Code:
     0: iload_1
     1: iload_2
     2: if_icmpne     9
     5: iconst_1
     6: goto          10
     9: iconst_0
    10: istore_3
    11: return

void ifVar(int, int);
  Code:
     0: iload_1
     1: iload_2
     2: if_icmpne     10
     5: iconst_1
     6: istore_3
     7: goto          12
    10: iconst_0
    11: istore_3
    12: return

Для пяти из шести функций оба компилятора генерируют один и тот же код. Единственная разница заключается в valReturn: javac генерирует goto для ireturn, а ecj генерирует ireturn. Для condReturn они оба генерируют от goto до ireturn. Для ifReturn они оба генерируют ireturn.

Означает ли это, что один из компиляторов оптимизирует один или несколько таких случаев? Можно подумать, что javac оптимизирует код ifReturn, но не может оптимизировать valReturn и condReturn, а ecj оптимизирует ifReturn и valReturn, но не оптимизирует condReturn.

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

В двух словах: нет, компиляторы исходного кода Java не оптимизируют этот случай, потому что на самом деле они ничего не оптимизируют. Они делают то, что от них требуют спецификации, но не более того. Разработчики javac и ecj просто выбрали несколько разные стратегии генерации кода для этих случаев (предположительно по более или менее произвольным причинам).

См. эти Переполнение стека вопросы еще несколько деталей.

(Пример: оба компилятора в настоящее время игнорируют флаг -O. Опции ecj явно говорят об этом: -O: optimize for execution time (ignored). javac больше не упоминает флаг и просто игнорирует его.)

person jcsahnwaldt Reinstate Monica    schedule 03.02.2019