Мы создали собственный механизм индексирования для проекта с поддержкой мультимедиа, написанного в C#
.
Механизм индексирования записывается в неуправляемом C++
и может содержать значительное количество неуправляемой памяти в виде коллекций и контейнеров std::
.
Каждый неуправляемый индексный экземпляр обертывается управляемым объектом; время жизни неуправляемого индекса контролируется временем жизни управляемой обертки.
Мы обеспечили (через настраиваемые, отслеживающие С++-распределители), что каждый байт, потребляемый внутри индексов, учитывается, и мы обновляем (10 раз в секунду) значение давления памяти управляемого коллектора мусора с дельтами это значение (положительный дельта-вызов GC.AddMemoryPressure()
, отрицательный дельта-вызов GC.RemoveMemoryPressure()
).
Эти индексы являются потокобезопасными, а может использоваться несколькими рабочими С#, поэтому для одного и того же индекса может использоваться несколько ссылок. По этой причине мы не можем свободно звонить Dispose()
и вместо этого полагаться на сборщик мусора для отслеживания обмена ссылками и в конечном итоге инициировать завершение индексов, когда они не используются рабочим процессом.
Теперь проблема заключается в том, что у нас заканчивается память. Полные коллекции на самом деле выполняются относительно часто, однако, с помощью профилировщика памяти, мы можем найти очень большое количество "мертвых" индексных экземпляров, которые хранятся в очереди финализации в момент, когда процесс исчерпается из памяти после исчерпания файл разбиения на страницы.
Мы можем фактически обойти проблему, если добавить поток сторожевого таймера, который вызывает GC::WaitForPendingFinalizers()
, а затем GC::Collect()
в условиях низкой памяти, однако из того, что мы прочитали, вызов GC::Collect()
вручную серьезно нарушает эффективность сбора мусора, и мы этого не хотим.
Мы даже добавили, безрезультатно, пессимистический фактор давления (до 4 раз), чтобы преувеличивать объем неуправляемой памяти, сообщаемой на стороне .net, чтобы узнать, можем ли мы уговорить сборщик мусора, чтобы быстрее освободить очередь, Кажется, что поток, обрабатывающий очередь, полностью не знает о давлении памяти.
В этот момент мы чувствуем, что нам нужно выполнить ручной подсчет ссылок до Dispose()
, как только счетчик достигнет нуля, но это, похоже, является излишним, особенно потому, что вся цель API давления в памяти - именно учет для таких случаев, как наша.
Некоторые факты:
- .Net версия 4.5
- Приложение находится в 64-битном режиме
- Сборщик мусора работает в режиме параллельного сервера.
- Размер индекса составляет ~ 800 МБ неуправляемой памяти
- В любой момент времени может быть до 12 "живых" индексов.
- Сервер имеет 64 ГБ оперативной памяти.
Любые идеи или предложения приветствуются