Ответ 1
Я возглавляю команду vs Team Roslyn. Все пулы объектов предназначены для снижения скорости распределения и, следовательно, частоты сбора мусора. Это происходит за счет добавления долгоживущих (gen 2) объектов. Это немного облегчает компиляцию, но основным эффектом является отзывчивость Visual Studio при использовании VB или С# IntelliSense.
почему существует так много реализаций ".
Нет быстрого ответа, но я могу думать о трех причинах:
- Каждая реализация выполняет несколько другую цель, и они настроены для этой цели.
- "Layering" - все пулы являются внутренними и внутренними деталями из уровня компилятора, на которые не может ссылаться слой Workspace или наоборот. У нас есть совместное использование кода через связанные файлы, но мы стараемся свести его к минимуму.
- Никакие большие усилия не привели к унификации реализаций, которые вы видите сегодня.
какая предпочтительная реализация
ObjectPool<T>
является предпочтительной реализацией и используется большая часть кода. Обратите внимание, что ObjectPool<T>
используется ArrayBuilder<T>.GetInstance()
и, возможно, самым большим пользователем пустых объектов в Roslyn. Поскольку ObjectPool<T>
так сильно используется, это один из случаев, когда мы дублируем код через слои через связанные файлы. ObjectPool<T>
настроен для максимальной пропускной способности.
На уровне рабочей области вы увидите, что SharedPool<T>
пытается совместно использовать объединенные экземпляры для непересекающихся компонентов, чтобы уменьшить общее использование памяти. Мы старались избегать того, чтобы каждый компонент создавал свой собственный пул, предназначенный для определенной цели, и вместо этого делился на основе типа элемента. Хорошим примером этого является StringBuilderPool
.
почему они выбрали размер пула 20, 100 или 128.
Обычно это результат профилирования и инструментария при типичных нагрузках. Обычно нам приходится балансировать между ставкой распределения ( "промахи" в пуле) и общим байтом в байтах в пуле. Эти два фактора играют:
- Максимальная степень parallelism (параллельные потоки, обращающиеся к пулу)
- Шаблон доступа, включающий перекрывающиеся выделения и вложенные распределения.
В великой схеме вещей память, хранящаяся в объекте в пуле, очень мала по сравнению с общей живой памятью (размером с кучей Gen 2) для компиляции, но мы также заботимся о том, чтобы не возвращать гигантские объекты (обычно большие коллекции) обратно в пул - мы просто опустим их на пол с призывом ForgetTrackedObject
В будущем я думаю, что одна область, которую мы можем улучшить, состоит в том, чтобы иметь пулы байт-массивов (буферов) с ограниченными длинами. Это поможет, в частности, реализовать реализацию MemoryStream в фазе испускания (PEWriter) компилятора. Эти MemoryStreams требуют смежных массивов байтов для быстрой записи, но они имеют динамический размер. Это означает, что они иногда нуждаются в изменении размера - обычно каждый раз удваивается. Каждое изменение размера является новым распределением, но было бы неплохо иметь возможность захватывать измененный буфер из выделенного пула и возвращать меньший буфер обратно в другой пул. Так, например, у вас будет пул для 64-байтовых буферов, другой для буферов на 128 байт и так далее. Общая память пула будет ограничена, но вы избегаете "взбалтывания" кучи GC по мере роста буферов.
Еще раз спасибо за вопрос.
Пол Харрингтон.