2010-04-13 19 views
7

Parece que C# es más rápido al agregar dos matrices de UInt16[] que al agregar dos matrices de int[]. Esto no tiene sentido para mí, ya que habría asumido que las matrices estarían alineadas con palabras, y por lo tanto int[] requeriría menos trabajo de la CPU, ¿no?¿Por qué las matrices UInt16 parecen agregar más rápido que las matrices en línea?

que corrió la prueba de código a continuación, y tiene los siguientes resultados:

Int for 1000 took 9896625613 tick (4227 msec) 
UInt16 for 1000 took 6297688551 tick (2689 msec) 

El código de prueba hace lo siguiente:

  1. crea dos matrices nombradas a y b, una vez.
  2. Los llena con datos aleatorios, una vez.
  3. Inicia un cronómetro.
  4. Agrega a y b, artículo por elemento. Esto se hace 1000 veces.
  5. Detiene el cronómetro.
  6. Informa cuánto tiempo tomó.

Esto se hace para int[] a, b y para UInt16 a,b. Y cada vez que ejecuto el código, las pruebas para las matrices UInt16 tardan entre un 30% y un 50% menos de tiempo que las matrices int. Me puedes explicar esto?

Aquí está el código, si quieres probar si usted mismo:

public static UInt16[] GenerateRandomDataUInt16(int length) 
{ 
    UInt16[] noise = new UInt16[length]; 
    Random random = new Random((int)DateTime.Now.Ticks); 
    for (int i = 0; i < length; ++i) 
    { 
     noise[i] = (UInt16)random.Next(); 
    } 

    return noise; 
} 

public static int[] GenerateRandomDataInt(int length) 
{ 
    int[] noise = new int[length]; 
    Random random = new Random((int)DateTime.Now.Ticks); 
    for (int i = 0; i < length; ++i) 
    { 
     noise[i] = (int)random.Next(); 
    } 

    return noise; 
} 

public static int[] AddInt(int[] a, int[] b) 
{ 
    int len = a.Length; 
    int[] result = new int[len]; 
    for (int i = 0; i < len; ++i) 
    { 
     result[i] = (int)(a[i] + b[i]); 
    } 
    return result; 
} 

public static UInt16[] AddUInt16(UInt16[] a, UInt16[] b) 
{ 
    int len = a.Length; 
    UInt16[] result = new UInt16[len]; 
    for (int i = 0; i < len; ++i) 
    { 
     result[i] = (ushort)(a[i] + b[i]); 
    } 
    return result; 
} 


public static void Main() 
{ 
    int count = 1000; 
    int len = 128 * 6000; 

    int[] aInt = GenerateRandomDataInt(len); 
    int[] bInt = GenerateRandomDataInt(len); 

    Stopwatch s = new Stopwatch(); 
    s.Start(); 
    for (int i=0; i<count; ++i) 
    { 
     int[] resultInt = AddInt(aInt, bInt); 
    } 
    s.Stop(); 
    Console.WriteLine("Int for " + count 
       + " took " + s.ElapsedTicks + " tick (" 
       + s.ElapsedMilliseconds + " msec)"); 

    UInt16[] aUInt16 = GenerateRandomDataUInt16(len); 
    UInt16[] bUInt16 = GenerateRandomDataUInt16(len); 

    s = new Stopwatch(); 
    s.Start(); 
    for (int i=0; i<count; ++i) 
    { 
     UInt16[] resultUInt16 = AddUInt16(aUInt16, bUInt16); 
    } 
    s.Stop(); 
    Console.WriteLine("UInt16 for " + count 
       + " took " + s.ElapsedTicks + " tick (" 
       + s.ElapsedMilliseconds + " msec)"); 


} 
+2

¿trató de ejecutar la adición de elementos en línea - sin llamar funciones AddXXX, pasando y volviendo matrices? ¿Has probado otro tamaño de matrices? –

+0

@Grzegorz Gierlik: buena pregunta de hecho. Tal como está, la rutina 'int' probablemente tenga que asignar el doble de memoria. –

+2

¿Qué hardware es eso? Llego a 15650 mseg y 14657 mseg (léase: sin diferencia significativa). Sospecho que el microbenchmark te está desanimando: los motores JIT y la optimización de máquinas virtuales son notorios por eso. La velocidad de adición de los números (16/32 bit) * será la misma * en cualquier CPU x86/x64 moderna. Sin embargo, números más grandes pueden tener una pequeña penalización en términos de llenar más líneas de caché y posiblemente requieran más buses para transferir. –

Respuesta

6

Lo que pasa es que ves una abstracción con goteras. UInt16 toma la mitad de la memoria que int (16 contra 32 bit).

Esto significa que el área de memoria ocupada por la matriz int16 ocupa la mitad del área que la int32. Así que más de esa área puede caber en la memoria caché del procesador y, por lo tanto, se puede acceder muy rápidamente.

Puede probar ese código en un procesador que tiene más memoria caché y la diferencia es probable que sea menor.

También intente con matrices mucho más grandes.

+0

Por el contrario, pruébelo con matrices más pequeñas que quepan dentro de una línea de caché. –

2

Las matrices son alineados por palabras, pero no hay ninguna razón por la cual las entradas de la matriz deben ser alineados por palabras.

1

Solo un SWAG: el menor uso de memoria de las matrices UInt16 ha mejorado las características de la memoria (GC, caché, quién sabe qué más). Como no parece haber demasiadas asignaciones, supongo que el caché es el factor principal.

Además, debe tener en cuenta que la evaluación comparativa puede ser un asunto complicado: parece que sus tiempos probablemente incluyan parte de la compilación JIT, lo que podría estar sesgando los resultados. Puede intentar invertir el orden en que prueba la matriz int con la matriz UInt16 y ver si los tiempos siguen o no.

Jon Skeet tiene (o tenía) un marco de referencia simple que codificó hace mucho tiempo cuando intentó tener en cuenta estos efectos. No sé si todavía está disponible (o incluso aplicable); tal vez él comente.

1

par de factores

1) También está cronometrando la generación de la resultante array..so sería interesante ver la cantidad de tiempo que se tardó en justo a la adición vs crear la matriz resultado que se pasa volver

2) sería interesante ver lo que se genera IL. Dado que su código es MUY directo (iterar y agregar), el compilador podría estar optimizando esto, tal vez rellenando varios uint16 en un registro más grande y haciendo múltiples adiciones por instrucción

+1

Lo revisé en Reflector, y eso no es lo que está sucediendo. El código es algorítmicamente virtualmente idéntico. Todas las operaciones son iguales pero ajustadas para sus tipos de datos apropiados. La única diferencia significativa es la adición de una operación 'conv.u2' después del' add' en el caso 'UInt16' (' add' devuelve un int, supongo - no puede encontrar la documentación para respaldarlo, aunque se encuentra razonar ya que esa es la forma en que C# también funciona). Si la diferencia estuviera en IL, esperaría que la versión 'UInt16' fuera más lenta, gracias a esa conversión adicional. Mi apuesta es la teoría de la falta de caché. – Dathan

1

No soy experto en .NET pero verificaría dos cosas:

  1. Pases matriz más grande (N elementos de tipo int) tarda más tiempo entonces array de N ushort elementos. Esto puede probarse usando varios tamaños de matrices y el estilo de codificación; vea mi comentario a la pregunta). Los números de tus pruebas se ajustan a esta teoría :).
  2. Adición de dos ushort las variables se pueden implementar como la adición de dos int con resultado de tipo int - sin comprobación desbordante. Y supongo que que maneja en el código cualquier tipo de excepción (incluida la excepción de desbordamiento) es que consume tiempo tarea. Esto se puede verificar en la documentación de .NET.
+1

FYI, VS 2008 utiliza el 'add' operación IL al compilar el anterior, y la [spec CIL] (http://download.microsoft.com/download/7/3/3/733AD403-90B2-4064-A81E- 01035A7FE13C/MS% 20Partition% 20III.pdf) indica que la operación 'add' no verifica el desbordamiento. – Dathan

Cuestiones relacionadas