paint-brush
Программное обеспечение процветает, если вы не убьете его первым: преждевременная оптимизация и рассказ о Java GCк@wasteofserver
553 чтения
553 чтения

Программное обеспечение процветает, если вы не убьете его первым: преждевременная оптимизация и рассказ о Java GC

к Frankie6m2024/04/06
Read on Terminal Reader

Слишком долго; Читать

Не переусердствуйте с оптимизацией, позвольте языку работать на вас. Историю рассказывайте. Обновления Java бесплатно повышают производительность. Всегда ориентируйтесь.
featured image - Программное обеспечение процветает, если вы не убьете его первым: преждевременная оптимизация и рассказ о Java GC
Frankie HackerNoon profile picture
0-item

Будет ли LinkedList быстрее? Должен ли я заменить «для каждого» на «итератор»? Должен ли этот ArrayList быть массивом? Эта статья появилась в ответ на настолько злобную оптимизацию, что она навсегда запечатлелась в моей памяти.


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


Преждевременная оптимизация — корень всех зол.


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


  1. как можно более описательный ; вам следует попытаться рассказать о намерениях так, как если бы вы писали историю.


  2. максимально оптимальный ; это означает, что вы должны знать основы языка и применять их соответствующим образом.

Максимально описательный

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


 int[10] array1; // bad int[10] numItems; // better int[10] backPackItems; // great

Уже по имени переменной можно сделать вывод о функциональности.


Хотя numItems является абстрактным, backPackItems многое расскажет вам об ожидаемом поведении.


Или скажем, у вас есть этот метод:


 List<Countries> visitedCountries() { if(noCountryVisitedYet) return new ArrayList<>(0); } // (...) return listOfVisitedCountries; }

Что касается кода, то он выглядит более или менее нормально.


Можем ли мы добиться большего? Мы определенно можем!


 List<Countries> visitedCountries() { if(noCountryVisitedYet) return Collections.emptyList(); } // (...) return listOfVisitedCountries; }

Чтение Collections.emptyList() гораздо более наглядно, чем new ArrayList<>(0);


Представьте, что вы впервые читаете приведенный выше код и натыкаетесь на защитную фразу , которая проверяет, действительно ли пользователь посещал страны. Кроме того, представьте, что это скрыто в длинном классе, чтение Collections.emptyList() определенно более наглядно, чем new ArrayList<>(0) , вы также убедитесь, что он неизменяем, чтобы клиентский код не мог его изменить.

Максимально оптимально

Знайте свой язык и используйте его соответственно. Если вам нужен double , нет необходимости заключать его в объект Double . То же самое касается использования List , если все, что вам действительно нужно, это Array .


Знайте, что вам следует объединить строки с помощью StringBuilder или StringBuffer , если вы разделяете состояние между потоками:


 // don't do this String votesByCounty = ""; for (County county : counties) { votesByCounty += county.toString(); } // do this instead StringBuilder votesByCounty = new StringBuilder(); for (County county : counties) { votesByCounty.append(county.toString()); }


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

Как убить его первым?

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


 // do not do this, ever! int i = 0; while (i<10000000) { // business logic if (i % 3000 == 0) { //prevent long gc try { Thread.sleep(0); } catch (Ignored e) { } } }

Адский взлом сборщика мусора!


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


  • Работает за счет побочных эффектов, Thread.sleep(0) не имеет смысла в этом блоке.
  • Работает, используя недостаток кода ниже по течению.
  • Для любого, кто унаследует этот код, он непонятен и волшебен.


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


Интерпретация будущего сборщика мусора Java, «придуманная» Microsoft Copilot


Как справиться с этим сборщиком мусора?

Если после всего этого сборщик мусора по-прежнему оказывает сопротивление, вы можете попробовать вот некоторые из вещей:


  • Если ваша служба настолько чувствительна к задержкам, что вы не можете разрешить GC, используйте «Epsilon GC» и вообще избегайте GC .
    -XX:+UnlockExperimentalVMOptions -XX:+UseEpsilonGC


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


  • Если ваша служба несколько чувствительна к задержке, но допустимый допуск допускает некоторую свободу действий , запустите GC1 и передайте ему что-то вроде -XX:MaxGCPauseTimeMillis=100 (по умолчанию — 250 мс).

  • Если проблема вызвана внешними библиотеками , скажем, одна из них вызывает System.gc() или Runtime.getRuntime().gc() которые являются сборщиками мусора, останавливающими мир, вы можете переопределить противоправное поведение, запустив с -XX:+DisableExplicitGC


  • Если вы используете JVM выше 11, попробуйте Z Garbage Collector (ZGC) , повышение производительности будет колоссальным! -XX:+UnlockExperimentalVMOptions -XX:+UseZGC . Вы также можете проверить этот тест JDK 21 GC .


ВЕРСИЯ НАЧАЛО

КОНЕЦ ВЕРСИИ

ПО УМОЛЧАНИЮ GC

Ява 1

Ява 4

Серийный сборщик мусора

Ява 5

Ява 8

Параллельный сборщик мусора

Ява 9

непрерывный

Сборщик мусора G1


Примечание 1: начиная с Java 15, ZGC готов к использованию , но вам все равно придется явно активировать его с помощью -XX:+UseZGC .


Примечание 2. Виртуальная машина рассматривает машины как серверные, если виртуальная машина обнаруживает более двух процессоров и размер кучи, превышающий или равный 1792 МБ. Если это не класс сервера, по умолчанию будет использоваться Serial GC .


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

u/shiphe — вам захочется прочитать комментарий полностью


Другие соответствующие библиотеки, которые вы можете изучить:

Java Microbenchmark Harness (JMH)

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

Java-Thread-Affinity

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

LMAX разрушитель

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

Netflix jvmquake

Идея jvmquake заключается в том, что когда с JVM что-то идет не так, вы хотите, чтобы она умерла, а не зависала. Пару лет назад я запускал моделирование в кластере HTCondor, который имел жесткие ограничения на память, и иногда задания зависали из-за ошибок «недостаточно памяти».


Эта библиотека заставляет JVM умереть, позволяя вам справиться с фактической ошибкой. В этом конкретном случае HTCondor автоматически перепланирует задание.

Последние мысли

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


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


И это хороший знак.



Правки и спасибо:


Также опубликовано на сайтеasteofserver.com.