Пытаясь улучшить производительность и найти узкие места, натравливаю на приложение профайлер. И нашел несколько "долгих" методов где до 80% занимает "Garbage Collection". Но на вид ничего военного. Как правильно копать дальше,
Здравствуйте, vladpol, Вы писали:
V>Пытаясь улучшить производительность и найти узкие места, натравливаю на приложение профайлер. И нашел несколько "долгих" методов где до 80% занимает "Garbage Collection". Но на вид ничего военного. Как правильно копать дальше,
Смотреть memory профайлером, какие объекты создаются.
Здравствуйте, vladpol, Вы писали:
V>Пытаясь улучшить производительность и найти узкие места, натравливаю на приложение профайлер. И нашел несколько "долгих" методов где до 80% занимает "Garbage Collection". Но на вид ничего военного. Как правильно копать дальше,
Проблема с профилированием GC в том, что она сборка мусора недетерминирована. Тут легко получить эффект бабочки. Вот пример: приложение выделяет много памяти в методе M1, но при этом не достаточно много, чтобы затриггерить долгую сборку мусора второго поколения. Потом вызывается метод М2, который выделят небольшой объект, но именно из-за него триггерится сборка второго поколения и профайлер показывает, что M2 тормоз и 80% отнимает GC.
Если GC влияет на производительность, то я обычно предпринимаю следующие шаги:
1. Выяснить, сколько именно времени занимает сборка мусора в процентном отношении ко времени работы приложения.
Качаем PerfView, запускаем его с включенной галочкой GC Collect Only на вкладке Collect. Это можно делать даже на проде, если нужно, поскольку подобный анализ очень и очень легковесный.
Запускаем сбор данных (кнопка Start Collection) и стартуем приложение. Через некоторое время (хз, что за приложение) нужно найти устойчивое состояние приложение, когда оно сделало работу или насосало достаточно данных для анализа.
Открываем отчет в PerfView и смотрим процент времени, которое отнимает GC от времени работы приложения.
Если значение сильно больше 10%, то, "Хьюстон, у нас проблемы" (с).
2. Если первый пункт показал, что у нас проблемы с памятью, то их нужно решать.
Тут все несколько сложнее, чем может показаться на первый взгляд.
GC — умный, но есть варианты. Первое, что нужно глянуть — это режим работы сборки мусора — Server GC vs. Workstation GC. Если на запускаемых машинах нет проблемы с памятью, а приложение — это некоторая форма числодробилки, батч-процесса, сервиса, то можно переключиться на Server GC с дефолтного Workstation GC. Может здорово помочь, поскольку сборка мусора будет менее агрессивной.
Затем думаем, как сделать приложению резекцию.
Я делал следующее: с помощью PerfView или с помощью более мощной тулы (я использовал .NET Memory Profiler от memprofiler.com) смотрим, кто жрет место.
Обычно можно найти группу объектов, которых сотни тысяч или даже миллионы, часть полей которых вообще-то не нужна. В этом случае, редко используемые поля выносятся в другие классы, чтобы разделить 'hot path', от 'corner case'-ов.
Так же смотрим, нет ли каких-то глупых и ненужных аллокаций: обилие строк — это нормально, но если их сильно много, то возможно есть проблемы с чрезмерным использованием конкатенации строк вручную. Опять же, могут быть аллокации делегатов: недавно, замена Method Group делегата, на лямбду помогло срезать пару процентов end-2-end времени исполнения, поскольку первые никогда не кэшируются, а создание делегата оказалось на очень горячем пути работы приложения.
Да, и тот же PerfView покажет, в чем причина триггеринга сборки мусора. Это даст понять, например, виновато ли приложение. Вполне возможно, что сборка мусора триггерится из-за нехватки памяти в системе, и нужно просто сделать резекцию винде и убрать кучу ненужных сервисов. Это маловероятно, но возможно. Профейлер памяти так же сможет показать, кто источник мусора, а также какой процент составляет мусор при каждой сборке. Вполне возможный вариант, что в приложении mid life crysis, когда короткоживущие объекты не убиваются при первой и второй сборках, а переходят во второе поколение, сборка которого является дорогой. Поэтому нужно обратить внимание в отчете PerfView на количество сборок мусора в разных поколениях и на среднее время каждой сборки. Это также поможет понять, что же происходит внутри приложения.
З.Ы. надеюсь, что что-то из этого поможет и я с радостью помогу с дальнейшим исследованием, как только ты сможешь выложить результаты профилирования (если такое возможно).
Здравствуйте, SergeyT., Вы писали:
ST>Если GC влияет на производительность, то я обычно предпринимаю следующие шаги: ST>Я делал следующее: с помощью PerfView или с помощью более мощной тулы (я использовал .NET Memory Profiler от memprofiler.com) смотрим, кто жрет место.
Хм, я обычно иду с другой стороны — смотрю аллокации. Вместе с heap allocations viewer для решарпера прям отлично работает. DotMemory точно умеет, вот как оно выглядит. И студийный профайлер вроде тоже научился.
Разбор по статическому снимку хорош для поиска утечек, но никак не избыточных аллокаций, как правило, именно они являются основной причиной частых GC.
И в дополнение к вам обоим: особенно на сервере/сервисах — я беру первым делом Process Hacker 2 (в отличие от Process Explorer) который без гемороя показывает .net performance counters и в разделе Memory смотрю на процент времени GC, количество сборок и размеры хипов. А так же остальные самые интересные разделы по вкусу (Threads например).
После чего если очень интересно кто (объекты каких типов, где спят потоки и т.п.) жрёт память — полный дамп памяти (всё тем же process hacker), затем windbg+sos. Дешево и сердито. Правда я практиковал именно такое, т.к. зачастую более суток надо, что-бы проблема начала проявляться.
PS: И в итоге, обнаружил как-то что клиентская либа толи к ксандре толи ещё к чему использует BlockingCollection в своём же Async методе. В итоге — какими-то чудесами почти все потоки пула — спят именно тут (потому что узел кластера по сети не доступен, но либа не видит что он уже поднялся и пора работать), а другие потоки наваливают работы в тред пул (посредством Task.Run). Но... голодания как-бы не происходит. В итоге жуткий перерасход памяти и 0 работы, на фоне нулевой загрузки ЦП.
Здравствуйте, fddima, Вы писали:
F>Здравствуйте, Sinix, Вы писали:
F> И в дополнение к вам обоим: особенно на сервере/сервисах — я беру первым делом Process Hacker 2 (в отличие от Process Explorer) который без гемороя показывает .net performance counters и в разделе Memory смотрю на процент времени GC, количество сборок и размеры хипов. А так же остальные самые интересные разделы по вкусу (Threads например). F> После чего если очень интересно кто (объекты каких типов, где спят потоки и т.п.) жрёт память — полный дамп памяти (всё тем же process hacker), затем windbg+sos. Дешево и сердито. Правда я практиковал именно такое, т.к. зачастую более суток надо, что-бы проблема начала проявляться.
PE может делать все тоже самое, в чем разница непонятно
Здравствуйте, Sharov, Вы писали:
S>PE может делать все тоже самое, в чем разница непонятно
Я же писал в посте выше: PE часто просто не вычитывает перформанс кантеры, хотя должен.
Здравствуйте, fddima, Вы писали:
F>Здравствуйте, Sharov, Вы писали:
S>>PE может делать все тоже самое, в чем разница непонятно F> Я же писал в посте выше: PE часто просто не вычитывает перформанс кантеры, хотя должен.
Ни разу не сталкивался с ситуацией, когда PE что-то там не вычитывает для дотнет приложения. Такое вообще может быть?
Здравствуйте, Sharov, Вы писали:
S>Ни разу не сталкивался с ситуацией, когда PE что-то там не вычитывает для дотнет приложения. Такое вообще может быть?
В апреле 2016 я брал свежий PE и опять наблюдал эту картинку. Это на сервере. На моей локальной тачке — он почти всегда работал. (*)
Как такое может быь — интересно не было, при наличие иного, на мой взгляд более удобного инструмента.
(*) Имхо — там что-то с правами, а точнее комбинация прав и юзеров, при том что ты админ — не важно. Я такое поведение наблюдал почти с самого начала.
Здравствуйте, fddima, Вы писали:
F>Здравствуйте, Sharov, Вы писали:
S>>Ни разу не сталкивался с ситуацией, когда PE что-то там не вычитывает для дотнет приложения. Такое вообще может быть? F> В апреле 2016 я брал свежий PE и опять наблюдал эту картинку. Это на сервере. На моей локальной тачке — он почти всегда работал. (*) F> Как такое может быь — интересно не было, при наличие иного, на мой взгляд более удобного инструмента.
Интересно, если PE не работал, то как PH работал? Я бы в случае отказа PE взял бы Perf. Monitor -- возни побольше, но как-то гибче и интерактивнее.
Здравствуйте, SergeyT., Вы писали:
ST>З.Ы. надеюсь, что что-то из этого поможет и я с радостью помогу с дальнейшим исследованием, как только ты сможешь выложить результаты профилирования (если такое возможно).
И про GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce; не забываем, если в наличии интенсивная работа с большими объектами.
Если нам не помогут, то мы тоже никого не пощадим.
Здравствуйте, IT, Вы писали:
IT>И про GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce; не забываем, если в наличии интенсивная работа с большими объектами.
+1 кстати.
Оффтоп: чтоб не потерялось: глянь вот эту ветку