Будет ли LinkedList быстрее? Должен ли я заменить «для каждого» на «итератор»? Должен ли этот ArrayList быть массивом? Эта статья появилась в ответ на настолько злобную оптимизацию, что она навсегда запечатлелась в моей памяти.
Прежде чем перейти к Java и способам устранения помех со стороны сборщика мусора или переключения контекста, давайте сначала взглянем на основы написания кода для себя в будущем.
Преждевременная оптимизация — корень всех зол.
Вы слышали это раньше; преждевременная оптимизация — корень всех зол. Ну, иногда. При написании программного обеспечения я твердо верю в то, что:
как можно более описательный ; вам следует попытаться рассказать о намерениях так, как если бы вы писали историю.
максимально оптимальный ; это означает, что вы должны знать основы языка и применять их соответствующим образом.
Ваш код должен выражать намерение, и во многом оно связано с тем, как вы называете методы и переменные.
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)
не имеет смысла в этом блоке.
Начинайте создавать что-то более сложное только в том случае, если после написания со всеми стандартными оптимизациями, которые предоставляет язык , вы столкнулись с узким местом. Но держитесь подальше от смесей, описанных выше.
Если после всего этого сборщик мусора по-прежнему оказывает сопротивление, вы можете попробовать вот некоторые из вещей:
Если ваша служба настолько чувствительна к задержкам, что вы не можете разрешить GC, используйте «Epsilon GC» и вообще избегайте GC .
-XX:+UnlockExperimentalVMOptions -XX:+UseEpsilonGC
Это, очевидно, увеличит вашу память до тех пор, пока вы не получите исключение OOM, так что либо это кратковременный сценарий, либо ваша программа оптимизирована для предотвращения создания объектов.
Если ваша служба несколько чувствительна к задержке, но допустимый допуск допускает некоторую свободу действий , запустите GC1 и передайте ему что-то вроде -XX:MaxGCPauseTimeMillis=100
(по умолчанию — 250 мс).
Если проблема вызвана внешними библиотеками , скажем, одна из них вызывает System.gc()
или Runtime.getRuntime().gc()
которые являются сборщиками мусора, останавливающими мир, вы можете переопределить противоправное поведение, запустив с -XX:+DisableExplicitGC
-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 — вам захочется прочитать комментарий полностью
Если вы оптимизируете ощущения без какого-либо реального сравнения, вы оказываете себе медвежью услугу. JMH — это де-факто библиотека Java для проверки производительности ваших алгоритмов. Используй это.
Привязка процесса к определенному ядру может улучшить попадание в кэш. Это будет зависеть от базового оборудования и того, как ваша программа обрабатывает данные. Тем не менее, эта библиотека настолько проста в реализации, что, если метод, нагружающий процессор, вам захочется его протестировать.
Это одна из тех библиотек, которую, даже если она вам не нужна, вам захочется изучить. Идея состоит в том, чтобы обеспечить параллелизм со сверхнизкой задержкой. Но то, как это реализовано, от механической симпатии до кольцевого буфера, привносит множество новых концепций. Я до сих пор помню, как впервые обнаружил это семь лет назад, проведя всю ночь, чтобы переварить это.
Идея jvmquake
заключается в том, что когда с JVM что-то идет не так, вы хотите, чтобы она умерла, а не зависала. Пару лет назад я запускал моделирование в кластере HTCondor, который имел жесткие ограничения на память, и иногда задания зависали из-за ошибок «недостаточно памяти».
Эта библиотека заставляет JVM умереть, позволяя вам справиться с фактической ошибкой. В этом конкретном случае HTCondor автоматически перепланирует задание.
Код, который заставил меня написать этот пост? Я написал гораздо хуже. Я все еще делаю. Лучшее, на что мы можем надеяться, — это постоянно меньше портить ситуацию.
Я ожидаю, что через несколько лет буду недоволен, глядя на свой собственный код.
И это хороший знак.
visitedCountries()
и подробное объяснение.Также опубликовано на сайтеasteofserver.com.