2010-02-05 7 views
13

que he visto en este blog:problema de optimización Odd bajo MSVC

http://igoro.com/archive/gallery-of-processor-cache-effects/

El "rareza", en parte 7 es lo que captó mi interés.

Mi primer pensamiento fue "Eso es solo que C# es raro".

No es que haya escrito el siguiente código de C++.

volatile int* p = (volatile int*)_aligned_malloc(sizeof(int) * 8, 64); 
memset((void*)p, 0, sizeof(int) * 8); 

double dStart = t.GetTime(); 

for (int i = 0; i < 200000000; i++) 
{ 
    //p[0]++;p[1]++;p[2]++;p[3]++; // Option 1 
    //p[0]++;p[2]++;p[4]++;p[6]++; // Option 2 
    p[0]++;p[2]++;     // Option 3 
} 

double dTime = t.GetTime() - dStart; 

El momento llego en mi 2.4 Ghz Core 2 Quad ir de la siguiente manera:

Option 1 = ~8 cycles per loop. 
Option 2 = ~4 cycles per loop. 
Option 3 = ~6 cycles per loop. 

Ahora esto es confuso. Mi razonamiento detrás de la diferencia se reduce a la latencia de escritura de la memoria caché (3 ciclos) en mi chip y la suposición de que la memoria caché tiene un puerto de escritura de 128 bits (esto es pura conjetura por mi parte).

Sobre esta base en la Opción 1: Incrementará p [0] (1 ciclo) y luego aumentará p [2] (1 ciclo) y luego tendrá que esperar 1 ciclo (para el caché) y luego p [1] (1 ciclo) luego espere 1 ciclo (para caché) luego p [3] (1 ciclo). Finalmente 2 ciclos para incrementos y saltos (aunque usualmente se implementan como decrementos y saltos). Esto da un total de 8 ciclos.

En la Opción 2: Puede incrementar p [0] yp [4] en un ciclo y luego incrementar p [2] yp [6] en otro ciclo. Luego 2 ciclos para restar y saltar. No se necesitan esperas en la memoria caché. Total 4 ciclos

En la opción 3: puede aumentar p [0] luego tiene que esperar 2 ciclos, luego incrementar p [2], luego restar y saltar. El problema es si configura el caso 3 para incrementar p [0] yp [4] TODAVÍA toma 6 ciclos (lo que hace que mi puerto de lectura/escritura de 128 bits salga del agua).

Entonces ... ¿alguien puede decirme qué demonios está pasando aquí? ¿Por qué el caso 3 toma más tiempo? También me gustaría saber lo que tengo mal en mi opinión anterior, ya que obviamente tengo algo mal. ¡Cualquier idea será altamente apreciada! :)

¡También sería interesante ver cómo GCC o cualquier otro compilador lo hace también!

Editar: La idea de Jerry Coffin me dio algunas ideas.

que he hecho algunas pruebas más (en una máquina diferente, por lo perdona el cambio en la frecuencia de administración) con y sin NOP y con diferentes cargos de NOP

case 2 - 0.46 00401ABD jne   (401AB0h) 

0 nops - 0.68 00401AB7 jne   (401AB0h) 
1 nop - 0.61 00401AB8 jne   (401AB0h) 
2 nops - 0.636 00401AB9 jne   (401AB0h) 
3 nops - 0.632 00401ABA jne   (401AB0h) 
4 nops - 0.66 00401ABB jne   (401AB0h) 
5 nops - 0.52 00401ABC jne   (401AB0h) 
6 nops - 0.46 00401ABD jne   (401AB0h) 
7 nops - 0.46 00401ABE jne   (401AB0h) 
8 nops - 0.46 00401ABF jne   (401AB0h) 
9 nops - 0.55 00401AC0 jne   (401AB0h) 

que he incluido los statetements salto para que pueda ver que la fuente y el destino están en una línea de caché. También puede ver que empezamos a notar la diferencia cuando tenemos 13 bytes o más de diferencia. Hasta que lleguemos a 16 ... entonces todo sale mal.

Así que Jerry no está bien (aunque su sugerencia SÍ ayuda un poco), sin embargo algo está sucediendo. Estoy cada vez más intrigado por intentar descubrir qué es ahora. Parece ser más un tipo de rareza de alineación de memoria en lugar de algún tipo de rareza de rendimiento de instrucción.

¿Alguien quiere explicar esto para una mente inquisitiva? : D

Editar 3: Interjay tiene un punto en el desenrollamiento que saca la edición anterior del agua. Con un bucle desenrollado, el rendimiento no mejora.Necesita agregar un nop para hacer que la brecha entre la fuente de salto y el destino sea la misma que para mi conteo de nop bueno arriba. El rendimiento todavía apesta. Es interesante que necesito 6 nudos para mejorar el rendimiento. Me pregunto cuántos nudos puede emitir el procesador por ciclo. Si es 3 entonces esa cuenta para la caché escribe latencia ... Pero, si eso es todo, ¿por qué está ocurriendo la latencia?

curioso y más curioso ...

+0

Fwiw, es fácil de conseguir GCC se ejecuta en casi cualquier sistema operativo para comparar, y se puede obtener libremente Compilador de Intel para algunos. La instalación de icc fue muy simple para mí en Ubuntu, solo recuerda que debes tener un chip Intel para aprovechar sus optimizaciones. –

+0

¿Qué es un GCi32? – jalf

+0

Lo único que se me ocurre es algún error en la programación de instrucciones. Dado que el ciclo es más corto, la CPU puede tener que detener algunos ciclos entre iteraciones para esperar a que se complete la escritura, lo que por alguna razón debe causar la desaceleración ** adicional **, haciéndolo más lento que el ciclo más largo.La latencia de caché parece afectar a todos los casos por igual y, como usted dice, el ancho del puerto R/W tampoco parece serlo. El único factor que puedo imaginar que podría causar que el ciclo más corto tome * más tiempo * es algún tipo de limitación de programación en la CPU. – jalf

Respuesta

3

Bueno, tuve una breve charla con un ingeniero de Intel sobre exactamente este problema y tiene esta respuesta:

Es claramente algo que ver con que las instrucciones terminan en la que unidades de ejecución, la rapidez con que las manchas de la máquina un problema de la tienda con carga , y qué tan rápida y elegantemente se trata de desenrollar la ejecución especulativa para hacerle frente (o si toma varios ciclos debido a algún conflicto interno). Pero dicho esto: necesitarías una pipetrace y un simulador muy detallados para resolver esto. Predecir el procesamiento de instrucciones fuera de servicio en estas tuberías es demasiado difícil para hacer en papel, incluso para las personas que diseñaron las máquinas. Para seglares, no hay esperanza en el infierno. ¡Lo siento!

Ao pensé que me gustaría añadir aquí la respuesta a esta pregunta y cerrar de una vez por todas :)

2

Esto no parece estar relacionada compilador. Al principio, pensé que podría deberse a trucos de compilación, como desenrollar bucles, pero al mirar el ensamblaje generado, MSVC 9.0 solo genera una traducción directa del código C++.

Opción 1:

[email protected]: 
    add DWORD PTR [esi], ecx 
    add DWORD PTR [esi+4], ecx 
    add DWORD PTR [esi+8], ecx 
    add DWORD PTR [esi+12], ecx 
    sub eax, ecx 
    jne SHORT [email protected] 

Opción 2:

[email protected]: 
    add DWORD PTR [esi], ecx 
    add DWORD PTR [esi+8], ecx 
    add DWORD PTR [esi+16], ecx 
    add DWORD PTR [esi+24], ecx 
    sub eax, ecx 
    jne SHORT [email protected] 

Opción 3:

[email protected]: 
    add DWORD PTR [esi], ecx 
    add DWORD PTR [esi+8], ecx 
    sub eax, ecx 
    jne SHORT [email protected] 
+0

Sí, llegué a la misma conclusión. Por lo tanto, busco las posibles rarezas del uso de la memoria caché, como el puerto de escritura, y las latencias de escritura de la memoria caché en el juego. – Goz

2

El conjunto de instrucciones x86 no es de ninguna manera representativa más de lo que realmente se está haciendo por la CPU. Las instrucciones se traducen a un lenguaje interno de máquina, el término "micro-op" se acuñó en los 486 días. Incluye cosas como el cambio de nombre de registro, la ejecución especulativa, las unidades de ejecución múltiples y su interacción con el caché, y no hay forma de predecir cuánto tiempo más debería durar algo. Los fabricantes de chips han dejado de publicar predicciones de tiempo de ciclo hace mucho tiempo. Sus diseños son un secreto comercial.

+0

Mientras que sí tiene razón, hasta cierto punto, todo en esto debería estar operando fuera de la caché. Esto me parece una importante advertencia de optimización y, por más secretos que sean, sus tiempos de ciclo son un 50% de éxito para hacer la mitad de trabajo es un gran éxito. Este es el tipo de cosas que a los gustos de Intel les gusta explicar a las personas porque hacen que sus fichas se vean bien cuando las personas escriben un código rápido. Estoy seguro de que debe ser explicado en alguna parte. – Goz

+0

@nobugz: Intel y AMD aún documentan latencias para instrucciones individuales. Por supuesto, hay muchas advertencias sobre cómo se programan y ejecutan las instrucciones en paralelo, y especialmente con respecto al subsistema de memoria/caché. – jalf

+1

@Goz: sospecho que no es un 50% de acierto, sino más bien un 33% de aceleración para el ciclo más largo. El cuerpo del bucle es tan corto para el caso 3 que es probable que se encuentre con muchas limitaciones de hardware (debe haber unos pocos ciclos entre las instrucciones de salto para verificar la suposición hecha por el predictor de bifurcación. Ponga la latencia del caché y cargar/almacenar dependencias, y sospecho que la velocidad en el caso 2 se debe a una optimización especial, que generalmente no se aplica, y por alguna razón no se puede usar para el caso más corto 3. – jalf

3

Sospecho que lo que está viendo es una rareza de predicción de bifurcación en lugar de algo relacionado con el almacenamiento en caché. En particular, en bastantes CPU, la predicción de bifurcaciones no funciona (tan bien | en absoluto) cuando tanto el origen como el destino de la bifurcación están en la misma línea de caché. Poner suficiente código dentro del bucle (incluso NOP) para obtener la fuente y el objetivo en diferentes líneas de caché dará una mejora sustancial en la velocidad.

+0

BP debe funcionar hasta cierto punto, o vería un rendimiento mucho peor que 6 ciclos por iteración.Pero sí, buen punto. Sugerí algún problema de predicción de bifurcación en otro comentario también, pero no sabía la limitación de "misma línea de caché". Suena como una buena suposición. – jalf

+0

@jalf: Si la memoria se sirve, el mensaje "no funciona en absoluto" solo estaba en el Pentium MMX (y posiblemente en el Pentium original). En los procesadores más nuevos funciona al menos en cierto grado, pero aún así no tan bien como para los saltos más largos. –

+0

Intenté desenrollar el lazo para la opción n. ° 3, de modo que tenga el mismo tamaño que las opciones n. ° 1 y n. ° 2, y el tiempo se mantuvo exactamente igual. Entonces esta no debe ser la causa. – interjay