Упражнения по Java-профилированию, настройке производительности и профилированию памяти - PullRequest
15 голосов
/ 04 августа 2010

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

  • Решить проблему и внедрить оптимизированный код
  • Продемонстрируйте решение, выполнив другой сеанс профилирования
  • В идеале, написать модульный тест, который демонстрирует увеличение производительности

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

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

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

Ответы [ 3 ]

6 голосов
/ 12 августа 2010

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

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

Петли:

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

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

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

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

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

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

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

Параллелизм:

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

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

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

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

1 голос
/ 04 августа 2010

Не игнорируйте этот метод , потому что он очень хорошо работает для любого языка и операционной системы, для по этим причинам . Например, здесь . Также попробуйте использовать примеры с I / O и значительной глубиной вызова. Не просто используйте маленькие связанные с процессором программы, такие как Мандельброт. Если вы возьмете этот пример C, который не слишком велик, и перекодируете его на Java, это должно проиллюстрировать большинство ваших идей.

Посмотрим:

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

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

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

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

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

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

0 голосов
/ 10 августа 2010

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

...