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

Java - вызов статических методов против ручного вложения - накладные расходы на производительность

Мне интересно, должен ли я вручную вводить небольшие методы, которые называются 100k - 1 миллион раз в некотором чувствительном к производительности алгоритме.

Во-первых, я думал, что, не встраиваясь, я налагаю некоторые накладные расходы, так как JVM должен будет определить, следует ли встроить этот метод (или даже не сделать этого).

Однако, на днях я заменил этот вручную встроенный код вызовом статических методов и увидел повышение производительности. Как это возможно? Означает ли это, что на самом деле нет накладных расходов, и что, позволяя JVM встроить "волю", фактически повышает производительность? Или это сильно зависит от платформы/архитектуры?

(Пример, в котором произошел рост производительности, заключался в замене подстановки массива (int t = a[i]; a[i] = a[j]; a[j] = t;) на вызов статического метода swap(int[] a, int i, int j). Еще один пример, в котором не было разницы в производительности, заключался в том, что я вложил в себя 10-линейный метод, который был 1000000 раз.)

4b9b3361

Ответ 1

Я видел нечто подобное. "Ручная вставка" не обязательно быстрее, программа результата может быть слишком сложной для анализатора оптимизации.

В вашем примере позвольте сделать некоторые дикие догадки. Когда вы используете метод swap(), JVM может анализировать тело метода и заключить, что, поскольку я и j не изменяются, хотя есть 4 доступа к массиву, вместо 4 требуется только проверка диапазона. локальная переменная t не требуется, JVM может использовать 2 регистра для выполнения задания без привлечения тэга t в стеке.

Позже тело swap() встроено в метод вызывающего абонента. Это происходит после предыдущей оптимизации, поэтому сохраняются все еще. Возможно даже, что тело метода вызывающего лица доказало, что я и j всегда находятся в пределах диапазона, поэтому также остаются 2 оставшихся проверки диапазона.

Теперь в ручной встроенной версии оптимизатор должен анализировать всю программу сразу, слишком много переменных и слишком много действий, это может не показать, что безопасно сохранять проверки диапазона или исключать локальную переменную t. В худшем случае эта версия может стоить еще 6 обращений к памяти для обмена, что является огромной накладной. Даже если читается только 1 дополнительная память, она по-прежнему очень заметна.

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

-

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

Ответ 2

JVM может очень эффективно создавать небольшие методы. Единственное преимущество, связанное с тем, что вы можете удалить код, упростите его, вставив его в текст.

JVM ищет определенные структуры и имеет некоторые "ручные кодированные" оптимизации, когда он распознает эти структуры. Используя метод свопинга, JVM может распознавать структуру и оптимизировать ее по-разному с конкретной оптимизацией.

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

Ответ 3

Извините за мой поздний ответ, но я только что нашел эту тему, и это привлекло мое внимание.

При разработке на Java попробуйте написать "простой и глупый" код. Причины:

  • оптимизация выполняется во время выполнения (поскольку сама компиляция выполняется во время выполнения). Компилятор все равно выяснит, какую оптимизацию нужно сделать, поскольку он компилирует не исходный код, который вы пишете, а внутреннее представление, которое он использует (несколько алгоритмов AST → VM → VM code... → , основанные на двоичном коде, выполняются при runtime компилятором JVM и интерпретатором JVM)
  • При оптимизации компилятор использует некоторые общие шаблоны программирования при принятии решения о том, что нужно оптимизировать; так помогите ему помочь вам! напишите частный статический (возможно, окончательный) метод, и он сразу выяснит, что он может:
    • встроить метод
    • скомпилировать его в собственный код

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

Заключение: имеет смысл оптимизировать код в C/С++ (поскольку перевод в двоичный файл делается статически), но одни и те же оптимизации обычно не имеют никакого значения в Java, где компилятор JIT-байтовый код, а не ваш источник код. И, кстати, из того, что я видел, javac даже не пытается оптимизировать:)

Ответ 4

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

Вероятно, профилировщик JVM легче видит узкое место, если оно находится в одном месте (статический метод), чем если оно выполняется несколько раз отдельно.

Ответ 5

Компилятор Jots Hotspot JIT способен встраивать много вещей, особенно в режим -server, хотя я не знаю, как вы получили фактическое повышение производительности. (Мое предположение заключалось бы в том, что inlining выполняется подсчетом вызова метода, и метод, заменяющий два значения, не вызывается слишком часто.)

Кстати, если его производительность действительно имеет значение, вы можете попробовать это для замены двух значений int. (Я не говорю, что это будет быстрее, но это может стоить пунт.)

a[i] = a[i] ^ a[j];
a[j] = a[i] ^ a[j];
a[i] = a[i] ^ a[j];