2010-05-03 13 views
29

Ciertos tipos de colecciones en .Net tienen un parámetro de constructor opcional "Capacidad inicial". Por ejemplo:Capacidad inicial de los tipos de colección, p. Dictionary, List

Dictionary<string, string> something = new Dictionary<string,string>(20); 

List<string> anything = new List<string>(50); 

Parece que no puedo encontrar cuál es la capacidad inicial predeterminada para estos objetos en MSDN.

Si sé que solo almacenaré aproximadamente 12 elementos en un diccionario, ¿no tiene sentido establecer la capacidad inicial en algo así como 20?

Mi razonamiento es, asumiendo que la capacidad crece como lo hace para un StringBuilder, que se duplica cada vez que se golpea la capacidad, y cada reasignación es costosa, ¿por qué no preestablecer el tamaño de algo que usted sabe que almacenará sus datos? , con un poco de espacio extra por si acaso? Si la capacidad inicial es 100, y sé que solo necesitaré una docena más o menos, parece que el resto de esa memoria está asignada para nada.

Respuesta

60

Si los valores predeterminados no están documentados, es probable que la capacidad inicial óptima sea , detalle de implementación y esté sujeta a cambios entre las versiones de la estructura. Es decir, no debe escribir código que asuma un cierto valor predeterminado.

Las sobrecargas de constructor con una capacidad son para casos en los que usted sabe mejor que la clase de la cantidad de elementos que se esperan. Por ejemplo, si crea una colección de 50 valores y sabe que este número nunca aumentará, puede inicializar la colección con una capacidad de 50, por lo que no tendrá que cambiar el tamaño si la capacidad predeterminada es menor.

Dicho esto, puede determinar los valores predeterminados utilizando Reflector. Por ejemplo, en .NET 4.0 (y probablemente versiones anteriores, así),

  • una Lista <T> se inicializa con una capacidad de 0. Cuando se añade el primer elemento, que se reinicializa a una capacidad de 4. Posteriormente, cada vez que se alcanza la capacidad, la capacidad se duplica.

  • a Diccionario <T> se ha inicializado con una capacidad de 0 también. Pero usa un algoritmo completamente diferente para aumentar la capacidad: aumenta la capacidad siempre para los números primos.

+6

Es probable que el cálculo del número primo se ocupe de las colisiones hash y el sondeo de las ubicaciones de entrada. Dependiendo del mecanismo interno, si solo almacenan un valor en cada hash, entonces necesitan ubicaciones de almacenamiento secundarias. Si no usa un primo, entonces puede encontrar un hash que no puede insertar. – Matt

+5

El diccionario usa el encadenamiento. El tamaño de la tabla de números primos compensa las pobres funciones hash. Las buenas funciones hash producen distribuciones aleatorias; la potencia de dos tamaños de tabla se usa en tablas hash modernas (la tabla hash .net se basaba en la tabla hash Java, que también usaba números primos, porque esa era una forma antigua de hacerlo, en los días de malas funciones hash). Debido a que Microsoft no proporcionó métodos integrados de combinación de hash, muchas funciones hash construidas en casa producen distribuciones pobres, por lo que la opción de número primo se compensa, a veces, hasta que la función hash produce múltiples números primos. –

8

Comprobación de la fuente, la capacidad por defecto para ambos List<T> y Dictionary<TKey, TValue> es 0.

+4

En .Net 4.5, la capacidad adicional es en realidad 3. Sí, el constructor predeterminado llama a un constructor sobrecargado con un valor de capacidad de 0, pero cuando el constructor llama al método Initialize, el tamaño se establece en 3. El tamaño real del el diccionario se determina a partir de una llamada a HashHelpers.GetPrime (capacidad) que devuelve el siguiente número primo que es mayor que la capacidad proporcionada. Por lo tanto, en .Net 4.5 la capacidad inicial de un diccionario es 3. Las listas tienen una capacidad predeterminada de 0, pero la capacidad va a 4 después de agregar el primer elemento a la lista. –

6

Si se conoce el tamaño, después diga ella; una optimización menor en la mayoría de los casos "pequeños", pero útil para colecciones más grandes. Me gustaría principalmente preocuparme por esto si estoy lanzando una cantidad "decente" de datos, ya que puede evitar tener que asignar, copiar y recopilar varias matrices.

La mayoría de las colecciones usan una estrategia de doblaje.

1

Otro problema con el ConcurrentDictionary (actualmente) y el uso de su constructor para establecer un tamaño inicial es que su rendimiento parece estar obstaculizado.

Por ejemplo, here's some example code and benchmarks Lo intenté.

Corrí el código en mi máquina y obtuve resultados similares.

Es decir, cuando se especifica el tamaño inicial, no hace nada para aumentar la velocidad del ConcurrentDictionary al agregar objetos. Técnicamente, creo que debería porque no tiene que tomar tiempo ni recursos para redimensionarse.

Sí, puede que no funcione tan rápido como un diccionario normal, pero aún esperaría que un ConcurrentDictionary con su tamaño inicial configurado tenga un rendimiento consistente y más rápido que un ConcurrentDictionary que no tiene su tamaño inicial establecido, especialmente cuando uno sabe de antemano la cantidad de elementos que se le agregarán.

Así que la moraleja de la historia es establecer el tamaño inicial no siempre garantiza una mejora en el rendimiento.

Cuestiones relacionadas