2009-03-09 9 views
13

Por alguna razón, parece que la operación Add en un HashSet es más lenta que la operación Contains cuando el elemento ya existe en la HashSet.rendimiento HashSet Añadir vs Contiene los elementos existentes

Aquí está la prueba:

Stopwatch watch = new Stopwatch(); 
    int size = 10000; 
    int iterations = 10000; 


    var s = new HashSet<int>(); 
    for (int i = 0; i < size; i++) { 
     s.Add(i); 
    } 

    Console.WriteLine(watch.Time(() => 
    { 
     for (int i = 0; i < size; i++) { 
      s.Add(i); 
     } 
    }, iterations)); 

    s = new HashSet<int>(); 
    for (int i = 0; i < size; i++) { 
     s.Add(i); 
    } 

    // outputs: 47,074,764 

    Console.WriteLine(watch.Time(() => 
    { 
     for (int i = 0; i < size; i++) { 
      if (!s.Contains(i)) 
       s.Add(i); 
     } 
    }, iterations)); 

    // outputs: 41,125,219 

¿Por qué es más rápido que ContainsAdd de elementos ya existentes?

Nota: Estoy usando esta extensión Stopwatch de otra pregunta.

public static long Time(this Stopwatch sw, Action action, int iterations) { 
     sw.Reset(); 
     sw.Start(); 
     for (int i = 0; i < iterations; i++) { 
      action(); 
     } 
     sw.Stop(); 

     return sw.ElapsedTicks; 
    } 

ACTUALIZACIÓN: Las pruebas internas ha puesto de manifiesto que la gran diff rendimiento sólo ocurre en la versión de 64 bits del marco .NET. Con la versión de 32 bits del marco contiene Parece que se ejecuta a idéntica velocidad para agregar (de hecho, parece que la versión con el contenido ejecuta un porcentaje más lento en algunas ejecuciones de prueba) En las versiones X64 del marco, la versión con el contenido parece correr aproximadamente un 15% más rápido.

+0

Para la prueba de 32 bits, ¿la ejecutó en una máquina de 32 bits, en una máquina virtual o especificando un objetivo x86 para la compilación y ejecutando el marco de 32 bits en WOW64? No sé si marcaría la diferencia, pero es posible. –

+0

Realicé mi prueba de 32 bits en una VM –

+0

¿Ha intentado mover la prueba de la función Contener Añadir encima de Agregar y luego medir el tiempo? Encontrará que la diferencia es insignificante. – Baz1nga

Respuesta

9

AddIfNotPresent hace una división adicional que contiene no realiza.Echar un vistazo a la IL por Contiene:

IL_000a: call  instance int32 class System.Collections.Generic.HashSet`1<!T>::InternalGetHashCode(!0) 
    IL_000f: stloc.0 
    IL_0010: ldarg.0 
    IL_0011: ldfld  int32[] class System.Collections.Generic.HashSet`1<!T>::m_buckets 
    IL_0016: ldloc.0 
    IL_0017: ldarg.0 
    IL_0018: ldfld  int32[] class System.Collections.Generic.HashSet`1<!T>::m_buckets 
    IL_001d: ldlen 
    IL_001e: conv.i4 
    IL_001f: rem 
    IL_0020: ldelem.i4 
    IL_0021: ldc.i4.1 
    IL_0022: sub 
    IL_0023: stloc.1 

Esto está calculando la ubicación cubo para el código hash. El resultado se guarda en la ubicación de memoria local 1.

AddIfNotPresent hace algo similar, pero también guarda el valor calculado en la ubicación 2, de modo que puede insertar el elemento en la tabla hash en esa posición si el elemento no existe. Lo hace guardar porque una de las ubicaciones se modifica más adelante en el ciclo que busca el artículo. De todos modos, aquí está el código relevante para AddIfNotPresent:

IL_0011: call  instance int32 class System.Collections.Generic.HashSet`1<!T>::InternalGetHashCode(!0) 
    IL_0016: stloc.0 
    IL_0017: ldloc.0 
    IL_0018: ldarg.0 
    IL_0019: ldfld  int32[] class System.Collections.Generic.HashSet`1<!T>::m_buckets 
    IL_001e: ldlen 
    IL_001f: conv.i4 
    IL_0020: rem 
    IL_0021: stloc.1 
    IL_0022: ldarg.0 
    IL_0023: ldfld  int32[] class System.Collections.Generic.HashSet`1<!T>::m_buckets 
    IL_0028: ldloc.0 
    IL_0029: ldarg.0 
    IL_002a: ldfld  int32[] class System.Collections.Generic.HashSet`1<!T>::m_buckets 
    IL_002f: ldlen 
    IL_0030: conv.i4 
    IL_0031: rem 
    IL_0032: ldelem.i4 
    IL_0033: ldc.i4.1 
    IL_0034: sub 
    IL_0035: stloc.2 

De todos modos, creo que la brecha extra es lo que está causando Añadir a tomar más tiempo que contiene. A primera vista, parece que esa división adicional podría tenerse en cuenta, pero no puedo decirlo con certeza sin pasar un poco más de tiempo descifrando el IL.

+1

Una pregunta interesante es por qué esto es mucho más lento en x64? –

1

Interesante, en mi máquina (Dell Latitude D630, dual-core 2.2 Ghz) Estoy obteniendo resultados casi idénticos para ambas pruebas a menos que corra el cronómetro en contra de una acción null antes de las pruebas. Por ejemplo:

corro las pruebas con el código exacto que usted ha dado en la pregunta:

Without Contains(): 8205794 
With Contains(): 8207596 

Si modifico el código de esta manera:

Después:

Stopwatch watch = new Stopwatch(); 
int size = 10000; 
int iterations = 10000; 

Añadir:

watch.Time(null, 0); 

Mis resultados son:

Without Contains(): 8019129 
With Contains(): 8275771 

Esto me parece algo extraño está pasando en el interior de la Stopwatch que está causando estas fluctuaciones.

+1

por favor si no puede confiar en el cronómetro ¿en quién puede confiar :) Acabo de repetir todo mi pruebas y obtengo resultados consistentes con mi original, la brecha incluso se agranda en el modo de lanzamiento. –

+1

Esto podría estar relacionado con que tenga una versión X64 del framework, probando en una VM –

+2

Confirmado que el problema parece estar relacionado con X64 –

1

Supongo que ejecutó su prueba de Visual Studio que provocó que se suprimiera la alineación de AddIfNotPresent en Add, por lo que está viendo el resultado de un nivel adicional de indirección en las llamadas a métodos.

Si compilar y ejecutar desde la línea de comandos para eliminar cualquier VS engaño ...

> csc /o+ /t:exe Program.cs 
> Program.exe 

... entonces no hay diferencia de rendimiento.

salidas de ejemplo (representativos de un mayor número de ensayos):

35036174 
35153818 

35225763 
34862330 

35047377 
35033323 
+0

no, acaba de ejecutar en versión fuera de VS y estoy descubriendo que es un 15% más rápido –

+0

tiene intenté la versión de línea de comando antigua y simple como la anterior? –

+0

Una gran diferencia que tiene mi máquina es que estoy ejecutando Vista X64, probará rápidamente en una máquina virtual en 32 bit –

Cuestiones relacionadas