2009-10-22 20 views
8

Tengo dos bucles for que básicamente miran en dos matrices diferentes (cada una tiene un tamaño de alrededor de 2-4k en el pico) y establecen un valor en una tercera matriz en función de estos valores. Por algún extraño motivo, hay una diferencia de dos factores entre el rendimiento de esta pieza de código, según en qué orden puse los dos lazos.¿Por qué esto mejora el rendimiento?

Esta es la primera configuración. Se ejecuta en ~ 150 milisegundos en mi PC:

public static int[] SchoolMultiplication(int[] a, int[] b, int numberBase) 
{ 
    List<double> times = new List<double>(); 
    TimeTest timeTest = new TimeTest(); 

    int aLen = a.Length; 
    int bLen = b.Length; 

    int[,] resultMatrix = new int[a.Length + b.Length, aLen]; 
    int[] result = new int[a.Length + b.Length]; 

    timeTest.Start(); 

    for (int horizontalIndex = 0; horizontalIndex < b.Length; horizontalIndex++) 
    { 
     for (int verticalIndex = 0; verticalIndex < a.Length; verticalIndex++) 

     { 
      resultMatrix[a.Length + b.Length - 1 - verticalIndex - horizontalIndex, verticalIndex] = a[a.Length - verticalIndex - 1] * b[b.Length - horizontalIndex - 1]; 
     } 
    } 

Ahora si cambio de nada más que el orden de los bucles como esto

for (int verticalIndex = 0; verticalIndex < a.Length; verticalIndex++) 
{ 
    for (int horizontalIndex = 0; horizontalIndex < b.Length; horizontalIndex++) 
{ 
     resultMatrix[a.Length + b.Length - 1 - verticalIndex - horizontalIndex, verticalIndex] = a[a.Length - verticalIndex - 1] * b[b.Length - horizontalIndex - 1]; 
    } 
} 

El tiempo total de ejecución del método se reduce a unos ~ 400 milisegundos . ¿Cómo un simple intercambio de orden de bucles mejora el rendimiento en casi un 300%? Supongo que se trata de algún tipo de funcionamiento de almacenamiento en caché o puntero?

+1

Vea aquí: http://stackoverflow.com/questions/997212/fastest-way-to-loop-through-a-2d-array –

+0

¿Cuáles son las longitudes de 'a' y' b'? –

+0

La respuesta es precisamente la que está en el enlace que proporcionó @Mike Daniels. es un ejemplo de problema/optimización relacionado con caché muy conocido. –

Respuesta

18

Es una cosa de arreglo de datos. Piense en la memoria como una matriz de una sola dimensión. Así es como las cosas están realmente organizadas en el disco (en lo que respecta a la computadora). Por lo tanto, cuando se crean matrices multidimensionales, cuando se cambia el orden de los bucles se cambia la forma en que se recorre la matriz. En lugar de leer en orden, estás saltando de posición en posición.


Una matriz multidimensional parece a esto:

3x3 matrix

Y como éste al ordenador. La forma óptima de desplazamiento tiene los índices siguientes en la flecha abajo: Linear traversed array

modo que cuando cambie usted matriz bucle la matriz es atravesada así: Array traversed by switched array loops

De este modo se obtiene más errores de caché y un algoritmo más pobre desempeño .

+11

... es como una matriz de sillas en un cine ... visitar cada silla recorriendo fila por fila es más rápido que columna por columna ... – Egon

+2

Sin Caché, sin embargo, el orden de atravesar a través de la memoria de acceso aleatorio (RAM) no importa (suponiendo que toda la matriz esté en la RAM) - "La palabra aleatorio se refiere al hecho de que cualquier dato puede ser devuelto en un tiempo constante, independientemente de su ubicación física y si está relacionado o no con la pieza de datos anterior. [1] "http://en.wikipedia.org/wiki/Random-access_memory –

1

Es muy probable que esté relacionado con los aciertos/errores de la caché. La diferencia radica en el acceso secuencial frente al disperso que se encuentra en un tamaño superior al tamaño de una línea de caché.

Para bucles C++ simples, también ayudaría a hacer los bucles hacia atrás para obtener un poco de rendimiento en el bucle. No estoy seguro de cómo se ajusta para .NET.

+0

¿Por qué ayuda hacer los bucles hacia atrás? –

+0

Si echas un vistazo al código de ensamblaje, la prueba es más fácil. Cuando se realiza un bucle hasta 0, la prueba es fácil porque disminuye y prueba la bandera Z de la CPU. Al comparar con otro límite, debe agregar un CMP adicional (para CPU X86 como ejemplo) – jdehaan

4

Localidad, localidad, localidad de datos. De Wikipedia (que lo dice mejor que yo):

Estructuras de datos lineales: La localización a menudo ocurre porque el código contiene bucles que tienden a hacer referencia a matrices u otras estructuras de datos por índices. Localidad secuencial, un caso especial de localidad espacial, se produce cuando los elementos de datos relevantes se organizan y se accede linealmente. Por ejemplo, la simple travesía de elementos en una matriz unidimensional, desde la dirección base hasta el elemento más alto, explotaría la localidad secuencial de la matriz en la memoria. [2] La localidad equidistante más general ocurre cuando el recorrido lineal se realiza sobre un área más larga de estructuras de datos adyacentes que tienen estructura y tamaño idénticos, y además de esto, no todas las estructuras están en acceso, sino solo los mismos elementos mutuamente correspondientes de las estructuras. Este es el caso cuando una matriz se representa como una matriz secuencial de filas y el requisito es acceder a una sola columna de la matriz.

0

Recuerdo haber leído sobre esto en Code Complete.En la mayoría de los lenguajes, los arreglos se configuran con el último índice configurado secuencialmente, por lo que está accediendo a los bytes directamente en una fila cuando itera sobre el último índice, en lugar de omitir al iterar sobre el primero.

+0

El último índice es aquel en el que los datos se ordenarían secuencialmente, no el primero. –

+0

Ah sí, tienes razón. –

1

Su intuición es correcta, es un problema de almacenamiento en caché. La publicación de @Mike Daniels a la pregunta siguiente básicamente describe exactamente el mismo problema. El segundo bit de código obtendrá muchos más éxitos de caché.

Fastest way to loop through a 2d array?

Pero, shhhh No se supone que se preocupan por la derecha del rendimiento? :)

+0

Este código se está escribiendo para una competencia de rendimiento en C#, por lo que es absolutamente crucial. No puedo creer que no haya pensado en el almacenamiento de memoria. –

+0

@Qua, sí solo estaba siendo gracioso. La línea del partido actual entre muchas personas parece ser que el rendimiento ya no importa. Pero eso es solo una tontería. – BobbyShaftoe

0

También creo que los tamaños relativos de las matrices a y b marcarían la diferencia.

Si a.length es grande y b.length es pequeño, la segunda opción debe ser más rápida. Por el contrario, si a.length es pequeño y b.length es grande, la primera opción sería más rápida. El problema es evitar el costo de configuración/desmontaje del ciclo interno.

Por cierto, ¿por qué tienes

int alen = a.length;

¿Pero luego también llama a.Length directamente? Parece que deberías elegir uno u otro.

+0

Al perfilar el código tratando de descubrir qué estaba sucediendo, jugué con el almacenamiento en caché de las longitudes de los arreglos, lo que está viendo son piezas dispersas de ese intento. No hubo ganancia de optimización, así que finalmente me deshice de ella. –

+0

¿Por qué si a.length es grande y b.length es pequeño, la segunda opción debería ser más rápida? –

Cuestiones relacionadas