Почему в Рослине так много реализаций Object Pooling?
ObjectPool - это тип, используемый в компиляторе Roslyn C# для повторного использования часто используемых объектов, которые обычно обновляются и мусор собирается очень часто. Это уменьшает количество и размер операций по сбору мусора, которые должны произойти.
Компилятор Roslyn, похоже, имеет несколько отдельных пулов объектов, и каждый пул имеет свой размер. Я хочу знать, почему существует так много реализаций, какова предпочтительная реализация и почему они выбрали размер пула 20, 100 или 128.
1 - SharedPools - хранит пул из 20 или 100 объектов, если используется BigDefault. Это также странно тем, что создает новый экземпляр PooledObject, который не имеет смысла, когда мы пытаемся объединять объекты, а не создавать и уничтожать новые.
// Example 1 - In a using statement, so the object gets freed at the end.
using (PooledObject<Foo> pooledObject = SharedPools.Default<List<Foo>>().GetPooledObject())
{
// Do something with pooledObject.Object
}
// Example 2 - No using statement so you need to be sure no exceptions are not thrown.
List<Foo> list = SharedPools.Default<List<Foo>>().AllocateAndClear();
// Do something with list
SharedPools.Default<List<Foo>>().Free(list);
// Example 3 - I have also seen this variation of the above pattern, which ends up the same as Example 1, except Example 1 seems to create a new instance of the IDisposable [PooledObject<T>][3] object. This is probably the preferred option if you want fewer GC's.
List<Foo> list = SharedPools.Default<List<Foo>>().AllocateAndClear();
try
{
// Do something with list
}
finally
{
SharedPools.Default<List<Foo>>().Free(list);
}
2 - ListPool и StringBuilderPool - Не строго отдельные реализации, но обертки вокруг реализации SharedPools, показанной выше специально для List и StringBuilder. Так что это повторно использует пул объектов, хранящихся в SharedPools.
// Example 1 - No using statement so you need to be sure no exceptions are thrown.
StringBuilder stringBuilder= StringBuilderPool.Allocate();
// Do something with stringBuilder
StringBuilderPool.Free(stringBuilder);
// Example 2 - Safer version of Example 1.
StringBuilder stringBuilder= StringBuilderPool.Allocate();
try
{
// Do something with stringBuilder
}
finally
{
StringBuilderPool.Free(stringBuilder);
}
3 - PooledDictionary и PooledHashSet - они используют ObjectPool напрямую и имеют совершенно отдельный пул объектов. Хранит бассейн из 128 предметов.
// Example 1
PooledHashSet<Foo> hashSet = PooledHashSet<Foo>.GetInstance()
// Do something with hashSet.
hashSet.Free();
// Example 2 - Safer version of Example 1.
PooledHashSet<Foo> hashSet = PooledHashSet<Foo>.GetInstance()
try
{
// Do something with hashSet.
}
finally
{
hashSet.Free();
}
Обновить
В.NET Core появились новые реализации пула объектов. Смотрите мой ответ на вопрос о реализации C# Object Pooling Pattern.
1 ответ
Я - лидер команды v-team в Roslyn. Все пулы объектов предназначены для уменьшения скорости выделения и, следовательно, частоты сборок мусора. Это происходит за счет добавления долгоживущих (2 поколения) объектов. Это немного помогает пропускной способности компилятора, но основное влияние на отзывчивость Visual Studio при использовании VB или C# IntelliSense.
почему так много реализаций ".
Там нет быстрого ответа, но я могу думать о трех причинах:
- Каждая реализация служит немного другой цели, и они настроены для этой цели.
- "Слои" - все пулы являются внутренними, и на внутренние детали из уровня компилятора нельзя ссылаться из уровня рабочей области или наоборот. У нас есть общий доступ к коду через связанные файлы, но мы стараемся свести его к минимуму.
- Не было сделано больших усилий для объединения реализаций, которые вы видите сегодня.
какая предпочтительная реализация
ObjectPool<T>
является предпочтительной реализацией и тем, что использует большинство кода. Обратите внимание, что ObjectPool<T>
используется ArrayBuilder<T>.GetInstance()
и это, вероятно, самый большой пользователь объединенных объектов в Roslyn. Так как ObjectPool<T>
так интенсивно используется, это один из случаев, когда мы дублировали код через слои через связанные файлы. ObjectPool<T>
настроен на максимальную пропускную способность.
На слое рабочей области вы увидите, что SharedPool<T>
пытается разделить объединенные экземпляры между несвязанными компонентами, чтобы уменьшить общее использование памяти. Мы старались избегать того, чтобы каждый компонент создавал свой собственный пул, предназначенный для определенной цели, и вместо этого делился на основе типа элемента. Хорошим примером этого является StringBuilderPool
,
почему они выбрали бассейн размером 20, 100 или 128.
Обычно это результат профилирования и инструментовки при типичных рабочих нагрузках. Обычно мы должны найти баланс между скоростью распределения ("пропуски" в пуле) и общим количеством активных байтов в пуле. Два фактора в игре:
- Максимальная степень параллелизма (параллельные потоки, обращающиеся к пулу)
- Схема доступа, включая перекрывающиеся выделения и вложенные выделения.
В общей схеме память, которую хранят объекты в пуле, очень мала по сравнению с общей оперативной памятью (размер кучи Gen 2) для компиляции, но мы также стараемся не возвращать гигантские объекты (обычно большие коллекции) обратно в бассейн - мы просто бросим их на пол с призывом ForgetTrackedObject
В будущем, я думаю, одна область, которую мы можем улучшить, - это иметь пулы байтовых массивов (буферов) с ограниченными длинами. Это поможет, в частности, реализации MemoryStream на этапе emit (PEWriter) компилятора. Этим MemoryStreams требуются непрерывные байтовые массивы для быстрой записи, но они имеют динамический размер. Это означает, что им иногда нужно изменить размер, обычно каждый раз удваивая размер. Каждое изменение размера - это новое распределение, но было бы неплохо иметь возможность получить буфер с измененным размером из выделенного пула и вернуть меньший буфер обратно в другой пул. Так, например, у вас будет пул для 64-байтовых буферов, другой для 128-байтовых буферов и так далее. Общая память пула будет ограничена, но вы избегаете "перемешивания" кучи GC по мере увеличения буферов.
Еще раз спасибо за вопрос.
Пол Харрингтон.