Я пишу финансовое приложение на C#, которое принимает сообщения из сети, переводит их в разные объекты в соответствии с типом сообщения и, наконец, применяет к ним бизнес-логику приложения.
Дело в том, что после применения бизнес-логики я уверен, что этот экземпляр мне больше никогда не понадобится. Вместо того, чтобы ждать, пока сборщик мусора освободит их, я хотел бы явно «удалить» их.
Есть ли лучший способ сделать это в C#, следует ли использовать пул объектов для повторного использования всегда одного и того же набора экземпляров или есть лучшая стратегия.
Цель состоит в том, чтобы избежать сбора мусора для использования любого процессора во время критичного по времени процесса.
В крайнем случае у меня есть одно 32-битное приложение (из-за зависимостей, которые не работают в 64-битной версии), поэтому, несмотря на то, что я использую 64-битную Windows с большим количеством оперативной памяти, чтобы избежать фрагментации памяти, мне пришлось храните списки-списки, чтобы каждый подсписок умещался в 64 КБ, чтобы .Net не размещался в «куче больших объектов». Таким образом, он может «сжимать» память во время сборки мусора, а не создавать большие «дыры» в адресном пространстве приложения размером 4 ГБ. Это конкретное приложение было проблематичным, потому что оно включало много выделений DirectX из собственной (не .NET) памяти. Сочетание распределений, отличных от .Net и .Net, привело к фрагментации.





Не удаляйте их сразу. Вызывать сборщик мусора для каждого объекта - плохая идея. Обычно вы, В самом деле, вообще не хотите связываться со сборщиком мусора, и даже критические по времени процессы - это просто условия гонки, ожидающие своего появления, если они настолько чувствительны.
Но если вы знаете, что у вашего приложения будут периоды занятости или малой нагрузки, вы можете попробовать более общий GC.Collect (), когда вы достигнете легкого периода, чтобы стимулировать очистку перед следующим периодом занятости.
Это нормально, если вы понимаете, что GC.Collect не обеспечивает детерминированную сборку мусора. Вы только указываете сборщику мусора запустить его цикл, и в этом случае он определяет, собирать ли память. Вызов GC.Collect не гарантирует сбор памяти.
Имейте в виду, что слишком частый вызов GC.Collect () может принести больше вреда, чем пользы: любые объекты, которые все еще имеют ссылки, будут перемещены из поколения 0 в поколение 1 или из поколения 1 в поколение 2. Любые такие объекты будут оставаться дольше / потребуют дополнительной работы для удаления GC. Вызывайте GC.Collect () ТОЛЬКО, когда НИКАКИХ сообщений все еще не обрабатываются, чтобы не было ссылок на какие-либо временные данные.
Смотрите здесь: http://msdn.microsoft.com/en-us/library/bb384202.aspx
Вы можете сказать сборщику мусора, что в данный момент делаете что-то важное, и он постарается быть к вам вежливым.
Хорошая запись в блоге на blogs.microsoft.co.il/blogs/sasha/archive/2008/08/10/…
Вы можете иметь ограниченное количество экземпляров каждого типа в пуле и повторно использовать уже выполненные с экземплярами. Размер пула будет зависеть от количества сообщений, которые вы будете обрабатывать.
Принуждение к GC.Collect () - вообще плохая идея, оставьте GC делать то, что у него лучше всего получается. Похоже, что лучшим решением было бы использовать пул объектов, который вы можете увеличивать при необходимости - я успешно использовал этот шаблон.
Таким образом, вы избегаете не только сборки мусора, но и регулярных затрат на выделение ресурсов.
Наконец, уверены ли вы, что сборщик мусора вызывает у вас проблемы? Вам, вероятно, следует измерить и доказать это, прежде чем внедрять какие-либо решения для экономии производительности - вы можете заставить себя ненужную работу!
Теоретически сборщик мусора не должен запускаться, если ваш процессор сильно загружен или если это действительно не нужно. Но если вам нужно, вы можете просто сохранить все свои объекты в памяти, возможно, один экземпляр, и никогда не очищать их, если вы не готовы. Вероятно, это единственный способ гарантировать, что сборщик мусора работает.
Если это абсолютно критично по времени, вам следует использовать детерминированную платформу, такую как C / C++. Даже вызов GC.Collect () будет генерировать циклы ЦП.
Ваш вопрос начинается с предложения, что вы хотите сэкономить память, но избавиться от объектов. Это оптимизация, критичная по пространству. Вам нужно решить, чего вы действительно хотите, потому что GC лучше оптимизирует эту ситуацию, чем человек.
Получите хорошее представление и почувствуйте, как ведет себя сборщик мусора, и вы поймете, почему то, о чем вы здесь думаете, не рекомендуется. если вам не нравится, что среда CLR тратит время на перестановку объектов в памяти много.
Вместо того, чтобы создавать новый экземпляр объекта каждый раз, когда вы получаете сообщение, почему бы вам не использовать повторно уже использованные объекты? Таким образом, вы не будете бороться со сборщиком мусора, и ваша память кучи не будет фрагментирована. **
Для каждого типа сообщений вы можете создать пул для хранения неиспользуемых экземпляров. Всякий раз, когда вы получаете сетевое сообщение, вы смотрите на тип сообщения, извлекаете ожидающий экземпляр из соответствующего пула и применяете свою бизнес-логику. После этого вы помещаете этот экземпляр объекта сообщения обратно в его пул.
Скорее всего, вы захотите «лениво загружать» свой пул экземплярами, чтобы ваш код легко масштабировался. Следовательно, класс пула должен будет определять, когда был извлечен нулевой экземпляр, и заполнять его перед передачей. Затем, когда вызывающий код помещает его обратно в пул, это настоящий экземпляр.
** «Объединение объектов - это шаблон для использования, который позволяет повторно использовать объекты, а не выделять и освобождать их, что помогает предотвратить фрагментацию кучи, а также дорогостоящее уплотнение сборщика мусора».
Попытки предугадать сборщик мусора - вообще очень плохая идея. В Windows сборщик мусора - поколенческий, и на него можно положиться, поскольку он довольно эффективен. Есть некоторые отмеченные исключения из этого общего правила - наиболее частым из них является возникновение одноразового события, которое, как вы знаете, приведет к смерти множества старых объектов - после того, как объекты будут переведены в Gen2 (самый долгоживущий) они склонны торчать.
В случае, о котором вы упомянули, вы звучите так, как будто вы генерируете несколько недолговечных объектов - это приведет к коллекциям Gen0. В любом случае они случаются относительно часто и являются наиболее эффективными. Вы можете избежать их, имея повторно используемый пул объектов, если хотите, но лучше всего точно убедиться, является ли сборщик мусора проблемой производительности, прежде чем предпринимать такие действия - профилировщик CLR является инструментом для этого.
Следует отметить, что сборщик мусора отличается в разных платформах .NET - на компактной платформе (которая работает на Xbox 360 и на мобильных платформах) это сборщик мусора, не связанный с поколениями, и поэтому вы должны быть намного осторожнее с тем, что мусор, который генерирует ваша программа.
Вы попадаете в себя - используйте пул объектов и повторно используйте эти объекты. Семантика вызовов этого объекта должна быть скрыта за фасадом фабрики. Вам нужно будет увеличить пул заранее определенным способом. Возможно, удваивайте размер каждый раз, когда он достигает предела - алгоритм высокого уровня или фиксированный процент. Я настоятельно рекомендую вам не вызывать GC.Collect ().
Когда нагрузка на пул станет достаточно низкой, вы можете сжать пул, что в конечном итоге вызовет сборку мусора - пусть CLR позаботится об этом.
Судя по звучанию, похоже, что вы говорите о детерминированной финализации (деструкторы в C++), которой нет в C#. Самое близкое, что вы найдете в C#, - это шаблон Disposable. В основном вы реализуете интерфейс IDisposable.
Базовый паттерн таков:
public class MyClass: IDisposable
{
private bool _disposed;
public void Dispose()
{
Dispose( true );
GC.SuppressFinalize( this );
}
protected virtual void Dispose( bool disposing )
{
if ( _disposed )
return;
if ( disposing )
{
// Dispose managed resources here
}
_disposed = true;
}
}
Опасно ли это, если ваш класс имеет другие ссылочные типы в качестве свойств? Будут ли они по-прежнему отправлены на сборку мусора или будут немедленно устранены после утилизации?
@jocull Dispose на самом деле не имеет ничего общего со сборкой мусора. Лучшее, что вы можете сделать, - это детерминированно избавиться от ресурсов, управляемых ООН (конечно, в этом случае у вас также должен быть финализатор), или установить некоторые внутренние ссылки на null (таким образом, делая их, возможно, подходящими для сбора). Единственная детерминированная часть - это неуправляемые ресурсы. Управляемая память управляется всегда - это предотвращает случайное удаление, например, того, что все еще имеет ссылки. Даже в стеке нет гарантированный. Это деталь реализации, а не часть спецификации.
Насколько интенсивно приложение? Я написал приложение, которое захватывает 3 звуковые карты (Managed DirectX, 44,1 кГц, стерео, 16 бит) блоками по 8 КБ и отправляет 2 из 3 потоков на другой компьютер через TCP / IP. Пользовательский интерфейс отображает измеритель уровня звука и (плавную) прокрутку названия / исполнителя для каждого из 3 каналов. Это работает на ПК с XP, 1,8 ГГц, 512 МБ и т. д. Приложение использует около 5% ЦП.
Я избегал ручного вызова методов GC. Но мне все же пришлось настроить несколько бесполезных вещей. Я использовал профайлер Ant от RedGate, чтобы отточить расточительные части. Отличный инструмент!
Я хотел использовать пул предварительно выделенных байтовых массивов, но управляемая сборка DX выделяет байтовые буферы внутри, а затем возвращает их в приложение. Оказалось, что не надо было.
Ответ полностью не связан с астротурфингом, но я читаю этот вопрос, потому что мы сталкиваемся с проблемой, аналогичной исходному плакату, и одной из первых упомянутых вещей были муравьи.
Буфер размером 8 КБ для стереофонических 16-битных потоков сэмплов 44,1 кГц обеспечивает задержку около 45 мс. Это далеко не в реальном времени. У вас будет гораздо больше проблем с получением менее 5 мсек, если вы не будете придерживаться нативного программирования без выделения памяти.
«Цель состоит в том, чтобы избежать сборки мусора для использования любого процессора во время критичного по времени процесса»
Вопрос: Если под критическим временем вы имеете в виду, что слушаете какое-то эзотерическое оборудование, и вы не можете позволить себе пропустить прерывание?
А: Если это так, то C# не подходит для использования, для этого вам нужен Ассемблер, C или C++.
Вопрос: Если под критическим временем, вы имеете в виду, когда в канале много сообщений, и вы не хотите, чтобы сборщик мусора замедлял работу?
А: Если так, то напрасно беспокоитесь. Судя по звукам вещей, ваши объекты очень недолговечны, это означает, что сборщик мусора будет перерабатывать их очень эффективно, без видимого отставания в производительности.
Тем не менее, единственный способ узнать наверняка - это протестировать его, настроить на выполнение в ночное время обработки постоянного потока тестовых сообщений, я буду ошеломлен, если ваша статистика производительности сможет определить, когда срабатывает сборщик мусора (и даже если вы можете заметьте это, я буду еще больше удивлен, если это действительно имеет значение).
В .Net пул полезен, когда либо (а) создание данного объекта происходит медленно, либо (б) в 32-битном приложении для уменьшения фрагментации памяти массива / списка / словаря и т. д., Который использует блок данных> 64 КБ, например > 8K элементов x 8 B на элемент или 16K элементов x 4 B на элемент. .Net никогда не перемещает (перемещает) такие большие блоки, что может привести к фрагментированной памяти, если вы каждый раз запрашиваете разные размеры. Безопаснее выделить # достаточно больших элементов заранее и сохранить их. (Но очищайте их поля, когда они не используются, чтобы сборщик мусора мог вернуть все, на что они указывают.) Если размер превышает выделенный, удвойте # элементов.