Профилирование Java, настройка производительности и профилирование памяти

Я собираюсь провести профилирование мастерских, настройку производительности, профилирование памяти, обнаружение утечки памяти и т.д. java-приложений, используя JProfiler и Eclipse Tptp. Мне нужен набор упражнений, которые я мог бы предложить участникам, где они могут: Используйте этот инструмент, чтобы выяснить проблему: узкое место, утечку памяти, субоптимальный код и т.д. Я уверен, что вокруг есть много опыта и реальных примеров.

  • Устранить проблему и реализовать оптимизированный код
  • Продемонстрируйте решение, выполнив еще один сеанс профилирования
  • В идеале напишите unit test, демонстрирующий прирост производительности

Проблемы или решения не должны быть чрезмерно сложными; должно быть возможно разрешить их в считанные минуты в лучшем случае и в течение нескольких часов в худшем случае. Некоторые интересные области для занятий:

  • Устранение утечек памяти
  • Оптимизировать циклы
  • Оптимизация создания и управления объектами
  • Оптимизация строковых операций
  • Устранение проблем, усугубляемых concurrency и concurrency узкими местами

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

Ответы

Ответ 1

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

Сценарий: у вас есть трудоемкая функция, которую вы хотите делать много раз для разных значений, но те же самые значения могут появляться снова (в идеале, не слишком долго после ее создания). Хорошим и простым примером являются пары URL-адресов веб-страниц, которые необходимо загрузить и обработать (для упражнений это должно быть, вероятно, смоделировано).

Циклы:

  • Вы хотите проверить, появляется ли какой-либо набор слов на страницах. Используйте свою функцию в цикле, но с тем же значением, псевдо-код:

    for (word : words) {
        checkWord(download(url))
    }
    

    Одно решение довольно просто, просто загрузите страницу перед циклом. Другое решение ниже.

Утечка памяти:

  • простой: вы также можете решить свою проблему с помощью своего кеша. В простейшем случае вы можете просто поставить результаты на (статическую) карту. Но если вы не помешаете этому, его размер будет расти бесконечно → утечка памяти.
    Возможное решение: используйте карту LRU. Скорее всего, производительность не будет сильно ухудшаться, но утечка памяти должна исчезнуть.
  • сложнее: скажем, вы используете предыдущий кеш с помощью WeakHashMap, где ключи являются URL-адресами (НЕ как строки, см. ниже), значения - это экземпляры класса, содержащего URL-адрес, загруженную страницу и что-то еще, Вы можете предположить, что это должно быть хорошо, но на самом деле это не так: поскольку значение (которое слабо ссылается) имеет ссылку на ключ (URL), ключ никогда не сможет быть очищен → хорошая утечка памяти.
    Решение: удалите URL-адрес из значения.
  • То же, что и раньше, но URL-адреса являются интернированными строками ( "для сохранения некоторой памяти, если мы снова будем иметь одни и те же строки" ), значение не относится к этому. Я не пробовал, но мне кажется, что это также вызовет утечку, потому что интернированные строки не могут быть GC-ed.
    Решение: не стажер, который также приведет к советам, которые вы не должны пропускать: не делайте преждевременную оптимизацию, так как это корень всех злых.

Создание объекта и строки:

  • Предположим, вы хотите отображать только текст страниц (~ удалить html-теги). Напишите функцию, которая делает это по очереди, и добавляет ее к растущему результату. Сначала результат должен быть строкой, поэтому добавление займет много времени и распределение объектов. Вы можете обнаружить эту проблему с точки зрения производительности (почему приложения настолько медленны) и с точки зрения создания объектов (почему мы создали так много строк, StringBuffers, массивов и т.д.).
    Решение: для получения результата используйте StringBuilder.

Concurrency:

  • Вы хотите ускорить загрузку всего файла путем параллельной загрузки/фильтрации. Создайте некоторые потоки и запустите свой код, используя их, но делайте все внутри большого синхронизированного блока (на основе кеша), просто "чтобы защитить кеш от проблем concurrency". Эффект должен состоять в том, что вы эффективно используете только один поток, так как все остальные ждут, чтобы получить блокировку в кеше.
    Решение: синхронизировать только вокруг операций кеша (например, использовать `java.util.collections.synchronizedMap())

  • Синхронизируйте все крошечные кусочки кода. Это должно убить производительность, возможно, предотвратить нормальное параллельное выполнение. Если вам повезет/умнее, вы также можете придумать мертвый замок. Мораль: синхронизация не должна быть специальной, на основе "не повредит", но хорошо продуманная вещь.

Бонусное упражнение:

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

Ответ 2

Не игнорируйте этот метод, потому что он отлично работает для любого языка и ОС, для этих причин. Пример здесь. Кроме того, попробуйте использовать примеры с ввода-вывода и значительную глубину вызова. Не просто используйте небольшие программы, связанные с процессором, такие как Mandelbrot. Если вы берете тот пример C, который не слишком велик, и перекодируйте его на Java, это должно иллюстрировать большинство ваших точек.

Посмотрим:

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

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

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

  • Оптимизация строковых операций.
    Используйте построитель строк, но не потайте код, который не использует твердый процент времени выполнения.

  • Concurrency.
    Concurrency имеет две цели.
    1) Производительность, но это работает только в той мере, в которой она позволяет нескольким аппаратным средствам одновременно запускаться. Если аппаратного обеспечения там нет, это не поможет. Больно. 2) Ясность выражения, поэтому, например, код пользовательского интерфейса не должен беспокоиться о том, что происходит тяжелый расчет или сетевой ввод-вывод в одно и то же время.

В любом случае, это не может быть подчеркнуто достаточно, не делайте никакой оптимизации, прежде чем вы докажете, что что-то занимает значительный процент времени.

Ответ 3

Я использовал JProfiler для профилирования нашего приложения. Но он не очень помог. Затем я использовал JHat.Using JHat, вы не можете видеть кучу в реальном времени. Вы должны взять кучу кучи, а затем проанализировать его. Использование OQL (Object Query Language) - хороший способ найти утечки кучи.