. Net Базовая сборка мусора при запуске множества экземпляров на одном компьютере Linux - PullRequest
2 голосов
/ 08 мая 2020

Мой первый вопрос по StackOverflow (после многих лет чтения различных тем).

Мне особенно трудно решить эту проблему, поэтому приветствуется любая помощь.

Я бегу много (50+) экземпляров вычислительно дорогостоящих. Net Ядро на Linux.

На машине достаточно ядер (64 ядра) и примерно 500 ГБ ОЗУ.

I есть один простой метод:

public static void Compute(Message m) {
// Does a lot of computation, memory allocation here
// All root objects are set to null at end (just to really make it clear!)
}

Этот метод вызывается несколько раз для каждого из 50+ экземпляров.

Я обнаружил, что объем памяти каждого процесса (в частности, VIRT с использованием top) продолжает увеличиваться после каждой итерации. В конце концов, каждый процесс пытается освободить память, но в целом доступная память для всей машины чрезвычайно мала (<1-2%). Наконец, запускаются сборщики мусора, но чрезвычайно низкий (иногда 0%) доступный объем памяти вызывает множество проблем в моих вычислениях, и некоторые из экземпляров «застревают» - в основном останавливаются и больше не выполняют никаких вычислений или прослушивания внешних событий. </p>

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

Я пробовал эти вещи (по отдельности и вместе ):

  1. Создание отдельного потока «жнеца», который вызывает G C .Collect (3, CollectionMode.Forced, true, true), et c.
  2. Обнуление каждого временного объекта в вычислении и немедленный вызов G C .Collect ()
  3. Резкое уменьшение количества экземпляров

Ни один из них не работал, и я довольно застрял.

Похоже, что. Net «держится» за память, если в ней нет крайней необходимости, но к тому времени уже слишком поздно, так как все другие экземпляры также делают то же самое . В конце концов, это вызывает большой G C всеми процессами, а затем я получаю проблему с мертвой / остановкой (некоторые экземпляры могут быть в процессе запуска потока или в сложной композиции LINQ) и просто молча d ie.

Технически, все созданные объекты и временные объекты не имеют корней и должны быть помечены как G C -able, но это не происходит плавно и периодически, как мне бы хотелось. Более того, вычисления существуют в совершенно другом методе, для которого нет никакой информации о состоянии, которая вообще сохраняется в других объектах.

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

Если бы я написал это на C, я бы сделал malloc / free, и память сбрасывалась бы после каждой итерации метода Compute (). Наконец, я не понимаю, что проблема заключается в endemi c to Linux, я наблюдаю такое же поведение (для одного экземпляра) и на Windows.

Заранее благодарим вас за ваши предложения.

В ответ на некоторые комментарии: 1. Я пробовал все виды режима G C .Collect () - принудительный, блокирующий, большой объект и т. Д. c., Gens 0-2 - некоторые незначительные различия, но ничего, что могло бы обеспечить комфорт для стабильного вычислительного процесса - 2) Я пробовал как рабочую станцию ​​(сначала меньше - затем достигло того же состояния), так и сервер

Ответы [ 2 ]

1 голос
/ 08 мая 2020

Было несколько предложений, направленных на решение этой проблемы - когда несколько экземпляров вычислительно-тяжелого процесса. Net Core 3.1 на 64-ядерной машине размером ~ 500 ГБ Linux сохраняли свою память и отказывались отказываться от нее. sh до победного конца (что создало массу проблем).

@ Piotr предложил тщательное прохождение всего распределения - наиболее сложное, но, вероятно, наиболее информативное в отношении того, что создается и сохраняется.

@ KonradKokosa предлагает работать со свойством времени выполнения GCHeapCount G C.

@ GuruStron, предлагая переключить свойство времени выполнения GCHardLimit / GCHardLimitPercentage, недавно представленное в. Net Core 3.0 .

@ GuruStron показалось самым простым для начала (вы устанавливаете процент от общей физической памяти, и я понимаю, что G C будет вызываться (не только для маркировки), когда этот порог будет достигнут .

Это решение сработало ИДЕАЛЬНО * 101 2 *. Я просто разделил максимальный размер памяти на ожидаемый максимальный размер изображения процесса и использовал его для изменения runtime.config.template, чтобы добавить параметр GCHardLimitPercentage = 3.5.

Это не только полностью решило проблему с памятью - проблема с удержанием, процесс вычислений теперь на 100% быстрее (в 2 раза).

Абсолютно замечательно, я провел последнюю неделю днем ​​и ночью, работая над этим, но благодаря решению.

Я также призываю других взглянуть на другие решения - я уже отстал в своем проекте!

Спасибо, Ceremony

0 голосов
/ 08 мая 2020

Прежде всего, не go вслепую в этот вопрос. Go с подробностями. Вам просто нужно проверить, в каких деталях используется эта память. Только Gen1 замечает интенсивное использование. Или, может быть, есть Gen3 - о котором вы не знали.

Игра со строками может создать огромный трафик памяти c. Но вы говорите, что память все ниже и ниже. И обычно запуск G C очищал бы память почти полностью, если бы это был только GEN1. Поскольку со временем он продолжает снижаться - это предполагает некоторую утечку памяти (возможно, из одной из используемых вами библиотек)

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

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

Там это разные приложения для профилирования памяти. Два самых популярных (оба платные, но оба с пробными версиями):

https://www.red-gate.com/products/dotnet-development/ants-memory-profiler/

https://www.jetbrains.com/dotmemory/

Вы должны запустить приложение, сгенерировать "Traffi c" в приложении и проверить, где находится память и с каким трафиком c вы имеете дело (и откуда)

Это - единственное, что могло бы привести вас к решению вашей проблемы.

На данный момент я могу только сказать, что это:

creating a separate "reaper" thread that invokes GC.Collect(3, CollectionMode.Forced, true, true), etc. 

не очень хорошая идея :) Их очень мало случаи, когда вы действительно хотите быть "умнее" алгоритмов G C и есть множество случаев, когда разработчики их используют и не должны :) Вместо того, чтобы заставлять G C выполнять эту работу - просто облегчите жизнь " "для него, так что он будет почти безработным;)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...