c# - studio - ¿Por qué hay tantas implementaciones de la agrupación de objetos en Roslyn?
framework para c# (1)
Soy el líder del equipo v de Roslyn. Todos los grupos de objetos están diseñados para reducir la tasa de asignación y, por lo tanto, la frecuencia de las recolecciones de basura. Esto viene a expensas de agregar objetos de larga vida (gen 2). Esto ayuda ligeramente al rendimiento del compilador, pero el efecto principal está en la capacidad de respuesta de Visual Studio cuando se usa VB o C # IntelliSense.
Por eso hay tantas implementaciones ".
No hay una respuesta rápida, pero puedo pensar en tres razones:
- Cada implementación tiene un propósito ligeramente diferente y se ajustan para ese propósito.
- "Capas": todas las agrupaciones son internas y los detalles internos de la capa del compilador pueden no estar referenciados desde la capa del área de trabajo o viceversa. Tenemos algunos códigos compartidos a través de archivos vinculados, pero intentamos mantenerlo al mínimo.
- No se ha hecho un gran esfuerzo para unificar las implementaciones que se ven hoy.
cual es la implementacion preferida
ObjectPool<T>
es la implementación preferida y lo que utiliza la mayoría de los códigos. Tenga en cuenta que ArrayBuilder<T>.GetInstance()
utiliza ArrayBuilder<T>.GetInstance()
y es probablemente el usuario más grande de objetos agrupados en Roslyn. Debido a que ObjectPool<T>
se usa mucho, este es uno de los casos en los que duplicamos código a través de las capas a través de archivos vinculados. ObjectPool<T>
está optimizado para un rendimiento máximo.
En la capa del área de trabajo, verá que SharedPool<T>
intenta compartir instancias agrupadas en componentes SharedPool<T>
para reducir el uso general de la memoria. Intentamos evitar que cada componente cree su propio grupo dedicado a un propósito específico y, en su lugar, compartir según el tipo de elemento. Un buen ejemplo de esto es el StringBuilderPool
.
Por eso escogieron un tamaño de piscina de 20, 100 o 128.
Por lo general, este es el resultado del perfilado y la instrumentación en cargas de trabajo típicas. Por lo general, tenemos que encontrar un equilibrio entre la tasa de asignación ("fallas" en el conjunto) y el total de bytes en vivo en el conjunto. Los dos factores en juego son:
- El grado máximo de paralelismo (subprocesos simultáneos que acceden al grupo)
- El patrón de acceso que incluye asignaciones superpuestas y asignaciones anidadas.
En el gran esquema de las cosas, la memoria de los objetos en el grupo es muy pequeña en comparación con la memoria en vivo total (tamaño del montón Gen 2) para una compilación, pero también tenemos cuidado de no devolver objetos gigantes (generalmente grandes colecciones) de vuelta a la piscina - simplemente las ForgetTrackedObject
al piso con una llamada a ForgetTrackedObject
Para el futuro, creo que un área que podemos mejorar es tener grupos de arreglos de bytes (buffers) con longitudes restringidas. Esto ayudará, en particular, a la implementación de MemoryStream en la fase de emisión (PEWriter) del compilador. Estos MemoryStreams requieren matrices de bytes contiguas para una escritura rápida pero tienen un tamaño dinámico. Eso significa que ocasionalmente necesitan cambiar el tamaño, generalmente duplicando su tamaño cada vez. Cada cambio de tamaño es una nueva asignación, pero sería bueno poder capturar un búfer de tamaño de un grupo dedicado y devolver el búfer más pequeño a un grupo diferente. Entonces, por ejemplo, tendría un grupo para buffers de 64 bytes, otro para buffers de 128 bytes y así sucesivamente. La memoria total de la agrupación se vería limitada, pero usted evita "batir" el montón del GC a medida que aumentan los búferes.
Gracias nuevamente por la pregunta.
Paul Harrington.
El ObjectPool es un tipo utilizado en el compilador de C # de Roslyn para reutilizar los objetos utilizados con frecuencia que normalmente se actualizarían y recolectarían la basura muy a menudo. Esto reduce la cantidad y el tamaño de las operaciones de recolección de basura que tienen que suceder.
El compilador de Roslyn parece tener algunos grupos de objetos separados y cada grupo tiene un tamaño diferente. Quiero saber por qué hay tantas implementaciones, cuál es la implementación preferida y por qué eligieron un tamaño de grupo de 20, 100 o 128.
1 - SharedPools : almacena un grupo de 20 objetos o 100 si se utiliza BigDefault. Esto también es extraño porque crea una nueva instancia de PooledObject, que no tiene sentido cuando intentamos agrupar objetos y no crear y destruir otros nuevos.
// 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 y StringBuilderPool : no implementaciones estrictamente separadas, sino envolturas alrededor de la implementación de SharedPools que se muestra arriba específicamente para List y StringBuilder. Así que esto reutiliza el conjunto de objetos almacenados en 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 y PooledHashSet : utilizan ObjectPool directamente y tienen un conjunto de objetos totalmente separado. Almacena un conjunto de 128 objetos.
// 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();
}
Actualizar
Hay nuevas implementaciones de agrupación de objetos en .NET Core. Ver mi respuesta para la pregunta de implementación de C # Object Pooling Pattern .