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 раз.)
Ответы
Ответ 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];