2017-02-05 19 views
1

Я знаю, что в этом коде:Является ли выражение return всегда вычисляемым или может быть оптимизировано компилятором?

public static void main(String[] args) {myMethod();} 
private Object myMethod() { 
    Object o = new Object(); 
    return o; 
} 

сборщик мусора уничтожит o после выполнения myMethod, поскольку возвращаемое значение myMethod не назначен, и, следовательно, нет никаких ссылок на него. Но что, если код что-то вроде:

public static void main(String[] args) {myMethod();} 
private Object myMethod() { 
    int i = 5; 
    return i + 10; 
} 

Будет ли компилятор даже заморачиваться обработку i + 10, видя, как возвращаемое значение не назначается?

И если i не был простым примитивным, но больше объект:

public static void main(String[] args) {myMethod();} 
private Object myMethod() { 
    return new LargeObject(); 
} 

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

Это было бы особенно важно, если выражение возвращаемый является сложным, но не имеет побочных эффектов, таких как:

public static void main(String[] args) { 
    List<Integer> list = new LinkedList(); 
    getMiddle(); 
} 
private Object getMiddle(List list) { 
    return list.get((int) list(size)/2); 
} 

Вызов этого метода в реальной жизни, не используя возвращаемое значение будет довольно бессмысленно, но это для примера.

Мой вопрос: учитывая эти примеры (конструктор объектов, операция на примитиве, вызов метода без побочных эффектов), может ли компилятор пропустить оператор возврата метода, если он видит, что значение не будет назначено ни на что ?

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

+0

Посмотрите на байтовый код (с 'javap'). Но я не думаю, что компилятор может оптимизировать вызов метода, и, следовательно, результат должен быть удален из возвращаемого стека независимо от того, использует ли его вызывающий. –

+0

@ElliottFrisch Проблемы в моем последнем абзаце, в сочетании с примером «операция на примитивах», отчасти потому, что я задал этот вопрос. Операция на примитивах может быть очень дорогостоящей чисто арифметической операцией, и по-прежнему не будет вызова метода. – MikaelF

+1

Был бы вызов метода, компилятор не может определить apriori, что метод не имеет побочных эффектов. –

ответ

2

Во-первых, давайте рассмотрим неправильное представление, которое очевидно в вашем вопросе, и некоторые комментарии.

В HotSpot (Oracle или OpenJDK) платформы Java, есть на самом деле два компиляторы, которые должны быть рассмотрены:

  • javac компилятор транслирует исходный код Java в байт-код. Это минимальная оптимизация. Фактически единственными значительными оптимизациями, которые он выполняет, является оценка выражений времени компиляции (что действительно необходимо для некоторых проверок времени компиляции) и повторная запись последовательностей конкатенации строк.

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

  • Компилятор JIT делает оптимизацию тяжелого веса. Он запускается во время выполнения программы.

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


Таким образом, в вашем примере, метод main вызывается один раз и myMethd вызывается один раз. Компилятор JIT даже не запускается, поэтому на самом деле байт-коды будут интерпретироваться. Но это круто. Для компилятора JIT потребуется больше времени, чтобы оптимизировать JIT-компилятор, чем вы могли бы сэкономить, запустив оптимизатор.

Но предположим, что оптимизатор сделал запустить ...

код компилятор JIT обычно имеет несколько стратегий:

  • Внутри метода, он оптимизирует на основе информации, локальной для метода.
  • Когда метод вызывается, он смотрит, может ли вызываемый метод быть inlined на сайте вызова. После вложения код затем может быть дополнительно оптимизирован в его контексте.

Так вот что такое Вероятно, произойдет.

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

  • Когда/если вызов метода на myMethod() является встроенным (например,в метод main(...), оптимизатор будет определять, что (например) эти утверждения

    int i = 5; 
        return i + 10; 
    

    являются ненужными в этом контексте, и оптимизировать его.

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

Совет:

  • Стоит думать о ли вы делаете ненужных вычислений на «валовой» уровне. Выбор правильного алгоритма или структуры данных часто имеет решающее значение.

  • На мелкозернистом уровне это вообще не стоит. Пусть компилятор JIT справится с этим.

    ПОКА у вас есть четкие доказательства того, что вы необходимость оптимизации (то есть критерий, который объективно слишком медленно), и четкие доказательства есть узкое место производительности в конкретной точке (например, профилирование результаты).

+0

Очевидно, что я не знал о javac/JIT. И поход-сбор также стал большим сюрпризом. Я всегда рассматривал процесс компилятора/интерпретатора как волшебный-подлый-джин-в-машине, но это многое проясняет. Спасибо, что потратили некоторое время на мой вопрос. – MikaelF

1

Вопросы типа «что сделает компилятор?» о Java немного наивны. Во-первых, есть два компилятора и переводчик. Статический компилятор делает некоторую простую оптимизацию, например , возможно,, оптимизируя любое арифметическое выражение, эффективно используя конечные операнды. Он конечно компилирует константы, литералы и константные выражения в литералы байт-кода. Реальная магия происходит во время выполнения.

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

Во время работы в контексте доступно гораздо больше информации. Для оптимизации интерпретатор времени выполнения плюс динамический дуэт компилятора могут учитывать такие вещи, как «Этот раздел кода даже стоит оптимизировать?» HotSpot и его ilk не будут оптимизировать создание экземпляра return new Foo();, если вызывающий абонент использует возвращаемое значение. Но они, возможно, сделают это по-другому, возможно, выбросят атрибуты в стек или даже в регистры, если позволяют обстоятельства. Таким образом, хотя объект существует в логической куче Java, он может существовать в другом месте на физических компонентах JVM.

Кто знает, произойдет ли конкретная оптимизация? Никто. Но они или что-то вроде них, или что-то еще более волшебное, могут произойти. Вероятно, оптимизация, которую выполняет HotSpot, отличается от и лучше того, что мы ожидаем или представляем, когда в своей мудрости он решает взять на себя труд, чтобы оптимизировать.

О, и во время выполнения HotSpot может привести к деоптимизации ранее оптимизированного кода. Это необходимо для сохранения семантики кода Java.

+0

К сожалению, я не могу принять более одного ответа, но спасибо за всю полезную информацию. – MikaelF