tipos sirve que primer para nivel memorias memoria informatica importancia caracteristicas caché cache c memory assembly nasm fma

sirve - Obtención de ancho de banda máximo en Haswell en el caché L1: solo obteniendo 62%



que es cache en informatica (1)

Estoy intentando obtener el ancho de banda completo en el caché L1 para la siguiente función en los procesadores Intel

float triad(float *x, float *y, float *z, const int n) { float k = 3.14159f; for(int i=0; i<n; i++) { z[i] = x[i] + k*y[i]; } }

Esta es la función triada de STREAM .

Obtengo aproximadamente el 95% del pico con los procesadores SandyBridge / IvyBridge con esta función (usando el ensamblaje con NASM). Sin embargo, usando Haswell solo logro el 62% del pico a menos que desenrolle el ciclo. Si me desenrollo 16 veces obtengo un 92%. No entiendo esto

Decidí escribir mi función en ensamblado usando NASM. El bucle principal en el ensamblaje se ve así.

.L2: vmovaps ymm1, [rdi+rax] vfmadd231ps ymm1, ymm2, [rsi+rax] vmovaps [rdx+rax], ymm1 add rax, 32 jne .L2

Resulta que en el manual de Optimizing Assembly de Agner Fog en los ejemplos 12.7-12.11 hace casi lo mismo (pero para y[i] = y[i] +k*x[i] ) para el Pentium M, Core 2, Sandy Bridge , FMA4 y FMA3. Logré reproducir su código más o menos por mi cuenta (en realidad, tiene un pequeño error en el ejemplo de FMA3 cuando transmite). Da recuentos de tamaño de instrucción, operaciones fusionadas, puertos de ejecución en tablas para cada procesador, excepto para FMA4 y FMA3. He intentado hacer esta tabla yo mismo para FMA3.

ports size μops-fused 0 1 2 3 4 5 6 7 vmovaps 5 1 ½ ½ vfmadd231ps 6 1 ½ ½ ½ ½ vmovaps 5 1 1 1 add 4 ½ ½ jne 2 ½ ½ -------------------------------------------------------------- total 22 4 ½ ½ 1 1 1 0 1 1

El tamaño se refiere a la longitud de la instrucción en bytes. La razón por la que las instrucciones add y jne tienen medio μop es que se fusionan en una macro-operación (no debe confundirse con μop fusion que todavía usa múltiples puertos) y solo necesitan el puerto 6 y un μop. La instrucción vfmadd231ps puede usar el puerto 0 o el puerto 1. Elegí el puerto 0. La carga vmovaps puede usar el puerto 2 o 3. Elegí 2 y vfmadd231ps usara el puerto 3 .. Para ser coherente con las tablas de Agner Fog y dado que creo que tiene más sentido decir que una instrucción que puede ir a diferentes puertos igualmente va a cada una la mitad del tiempo, vmovaps 1/2 para los puertos vmovaps y vmadd231ps puede ir a.

Según esta tabla y el hecho de que todos los procesadores Core2 pueden hacer cuatro μops en cada ciclo de reloj, parece que este ciclo debería ser posible en cada ciclo de reloj, pero no he logrado obtenerlo. ¿Puede alguien explicarme por qué no puedo acercarme al ancho de banda máximo para esta función en Haswell sin desenrollarlo? ¿Es esto posible sin desenrollar y, de ser así, cómo se puede hacer? Permítanme aclarar que realmente estoy tratando de maximizar el ILP para esta función (no solo quiero el ancho de banda máximo), así que esa es la razón por la que no quiero desenrollar.

Editar: Aquí hay una actualización ya que Iwillnotexist Idonotexist mostró usando IACA que las tiendas nunca usan el puerto 7. Logré romper la barrera del 66% sin desenrollar y hacer esto en un ciclo de reloj cada iteración sin desenrollar (teóricamente). Primero abordemos el problema de la tienda.

Stephen Canon mencionó en el comentario que la Unidad de generación de direcciones (AGU) en el puerto 7 solo puede manejar operaciones simples como [base + offset] y no [base + index] . En el manual de referencia de optimización de Intel, lo único que encontré fue un comentario en el puerto 7 que dice "Simple_AGU" sin una definición de lo que significa simple. Pero luego, Idonnotexist Idonotexist descubrió en los comentarios de IACA que este problema ya se mencionó hace seis meses en el que un empleado de Intel escribió el 11/03/2014:

Port7 AGU solo puede funcionar en tiendas con una dirección de memoria simple (sin registro de índice).

Stephen Canon sugiere "usar la dirección de la tienda como el desplazamiento para los operandos de carga". He intentado esto así

vmovaps ymm1, [rdi + r9 + 32*i] vfmadd231ps ymm1, ymm2, [rsi + r9 + 32*i] vmovaps [r9 + 32*i], ymm1 add r9, 32*unroll cmp r9, rcx jne .L2

De hecho, esto hace que la tienda use port7. Sin embargo, tiene otro problema que es que el vmadd231ps no se fusiona con la carga que puede ver desde IACA. También necesita adicionalmente la instrucción cmp que mi función original no necesitaba. Por lo tanto, la tienda usa un micro-op menos, pero el cmp (o más bien add ya que la macro cmp fusiona con el jne ) necesita uno más. IACA informa un rendimiento de bloque de 1.5. En la práctica, esto solo representa alrededor del 57% del pico.

Pero encontré una manera de hacer que la instrucción vmadd231ps fusionara con la carga. Esto solo se puede hacer utilizando matrices estáticas con direccionamiento [dirección absoluta de 32 bits + índice] como este. Evgeny Kluev original sugirió esto .

vmovaps ymm1, [src1_end + rax] vfmadd231ps ymm1, ymm2, [src2_end + rax] vmovaps [dst_end + rax], ymm1 add rax, 32 jl .L2

Donde src1_end , src2_end y dst_end son las direcciones finales de las matrices estáticas.

Esto reproduce la tabla en mi pregunta con cuatro micro-operaciones fusionadas que esperaba. Si coloca esto en IACA, informa un rendimiento de bloque de 1.0. En teoría, esto debería funcionar tan bien como las versiones SSE y AVX. En la práctica, obtiene aproximadamente el 72% del pico. Eso rompe la barrera del 66%, pero todavía está muy lejos del 92% que obtengo desenrollando 16 veces. Entonces, en Haswell, la única opción para acercarse al pico es desenrollar. Esto no es necesario en Core2 a través de Ivy Bridge pero está en Haswell.

End_edit:

Aquí está el código C / C ++ Linux para probar esto. El código NASM se publica después del código C / C ++. Lo único que tiene que cambiar es el número de frecuencia. En la línea double frequency = 1.3; reemplace 1.3 con cualquiera que sea la frecuencia de funcionamiento (no nominal) de sus procesadores (que en el caso de un i5-4250U con turbo desactivado en el BIOS es de 1.3 GHz).

Compilar con

nasm -f elf64 triad_sse_asm.asm nasm -f elf64 triad_avx_asm.asm nasm -f elf64 triad_fma_asm.asm g++ -m64 -lrt -O3 -mfma tests.cpp triad_fma_asm.o -o tests_fma g++ -m64 -lrt -O3 -mavx tests.cpp triad_avx_asm.o -o tests_avx g++ -m64 -lrt -O3 -msse2 tests.cpp triad_sse_asm.o -o tests_sse

El código C / C ++

#include <x86intrin.h> #include <stdio.h> #include <string.h> #include <time.h> #define TIMER_TYPE CLOCK_REALTIME extern "C" float triad_sse_asm_repeat(float *x, float *y, float *z, const int n, int repeat); extern "C" float triad_sse_asm_repeat_unroll16(float *x, float *y, float *z, const int n, int repeat); extern "C" float triad_avx_asm_repeat(float *x, float *y, float *z, const int n, int repeat); extern "C" float triad_avx_asm_repeat_unroll16(float *x, float *y, float *z, const int n, int repeat); extern "C" float triad_fma_asm_repeat(float *x, float *y, float *z, const int n, int repeat); extern "C" float triad_fma_asm_repeat_unroll16(float *x, float *y, float *z, const int n, int repeat); #if (defined(__FMA__)) float triad_fma_repeat(float *x, float *y, float *z, const int n, int repeat) { float k = 3.14159f; int r; for(r=0; r<repeat; r++) { int i; __m256 k4 = _mm256_set1_ps(k); for(i=0; i<n; i+=8) { _mm256_store_ps(&z[i], _mm256_fmadd_ps(k4, _mm256_load_ps(&y[i]), _mm256_load_ps(&x[i]))); } } } #elif (defined(__AVX__)) float triad_avx_repeat(float *x, float *y, float *z, const int n, int repeat) { float k = 3.14159f; int r; for(r=0; r<repeat; r++) { int i; __m256 k4 = _mm256_set1_ps(k); for(i=0; i<n; i+=8) { _mm256_store_ps(&z[i], _mm256_add_ps(_mm256_load_ps(&x[i]), _mm256_mul_ps(k4, _mm256_load_ps(&y[i])))); } } } #else float triad_sse_repeat(float *x, float *y, float *z, const int n, int repeat) { float k = 3.14159f; int r; for(r=0; r<repeat; r++) { int i; __m128 k4 = _mm_set1_ps(k); for(i=0; i<n; i+=4) { _mm_store_ps(&z[i], _mm_add_ps(_mm_load_ps(&x[i]), _mm_mul_ps(k4, _mm_load_ps(&y[i])))); } } } #endif double time_diff(timespec start, timespec end) { timespec temp; if ((end.tv_nsec-start.tv_nsec)<0) { temp.tv_sec = end.tv_sec-start.tv_sec-1; temp.tv_nsec = 1000000000+end.tv_nsec-start.tv_nsec; } else { temp.tv_sec = end.tv_sec-start.tv_sec; temp.tv_nsec = end.tv_nsec-start.tv_nsec; } return (double)temp.tv_sec + (double)temp.tv_nsec*1E-9; } int main () { int bytes_per_cycle = 0; double frequency = 1.3; //Haswell //double frequency = 3.6; //IB //double frequency = 2.66; //Core2 #if (defined(__FMA__)) bytes_per_cycle = 96; #elif (defined(__AVX__)) bytes_per_cycle = 48; #else bytes_per_cycle = 24; #endif double peak = frequency*bytes_per_cycle; const int n =2048; float* z2 = (float*)_mm_malloc(sizeof(float)*n, 64); char *mem = (char*)_mm_malloc(1<<18,4096); char *a = mem; char *b = a+n*sizeof(float); char *c = b+n*sizeof(float); float *x = (float*)a; float *y = (float*)b; float *z = (float*)c; for(int i=0; i<n; i++) { x[i] = 1.0f*i; y[i] = 1.0f*i; z[i] = 0; } int repeat = 1000000; timespec time1, time2; #if (defined(__FMA__)) triad_fma_repeat(x,y,z2,n,repeat); #elif (defined(__AVX__)) triad_avx_repeat(x,y,z2,n,repeat); #else triad_sse_repeat(x,y,z2,n,repeat); #endif while(1) { double dtime, rate; clock_gettime(TIMER_TYPE, &time1); #if (defined(__FMA__)) triad_fma_asm_repeat(x,y,z,n,repeat); #elif (defined(__AVX__)) triad_avx_asm_repeat(x,y,z,n,repeat); #else triad_sse_asm_repeat(x,y,z,n,repeat); #endif clock_gettime(TIMER_TYPE, &time2); dtime = time_diff(time1,time2); rate = 3.0*1E-9*sizeof(float)*n*repeat/dtime; printf("unroll1 rate %6.2f GB/s, efficency %6.2f%%, error %d/n", rate, 100*rate/peak, memcmp(z,z2, sizeof(float)*n)); clock_gettime(TIMER_TYPE, &time1); #if (defined(__FMA__)) triad_fma_repeat(x,y,z,n,repeat); #elif (defined(__AVX__)) triad_avx_repeat(x,y,z,n,repeat); #else triad_sse_repeat(x,y,z,n,repeat); #endif clock_gettime(TIMER_TYPE, &time2); dtime = time_diff(time1,time2); rate = 3.0*1E-9*sizeof(float)*n*repeat/dtime; printf("intrinsic rate %6.2f GB/s, efficency %6.2f%%, error %d/n", rate, 100*rate/peak, memcmp(z,z2, sizeof(float)*n)); clock_gettime(TIMER_TYPE, &time1); #if (defined(__FMA__)) triad_fma_asm_repeat_unroll16(x,y,z,n,repeat); #elif (defined(__AVX__)) triad_avx_asm_repeat_unroll16(x,y,z,n,repeat); #else triad_sse_asm_repeat_unroll16(x,y,z,n,repeat); #endif clock_gettime(TIMER_TYPE, &time2); dtime = time_diff(time1,time2); rate = 3.0*1E-9*sizeof(float)*n*repeat/dtime; printf("unroll16 rate %6.2f GB/s, efficency %6.2f%%, error %d/n", rate, 100*rate/peak, memcmp(z,z2, sizeof(float)*n)); } }

El código NASM que usa el Sistema V AMD64 ABI.

triad_fma_asm.asm:

global triad_fma_asm_repeat ;RDI x, RSI y, RDX z, RCX n, R8 repeat ;z[i] = y[i] + 3.14159*x[i] pi: dd 3.14159 ;align 16 section .text triad_fma_asm_repeat: shl rcx, 2 add rdi, rcx add rsi, rcx add rdx, rcx vbroadcastss ymm2, [rel pi] ;neg rcx align 16 .L1: mov rax, rcx neg rax align 16 .L2: vmovaps ymm1, [rdi+rax] vfmadd231ps ymm1, ymm2, [rsi+rax] vmovaps [rdx+rax], ymm1 add rax, 32 jne .L2 sub r8d, 1 jnz .L1 vzeroupper ret global triad_fma_asm_repeat_unroll16 section .text triad_fma_asm_repeat_unroll16: shl rcx, 2 add rcx, rdi vbroadcastss ymm2, [rel pi] .L1: xor rax, rax mov r9, rdi mov r10, rsi mov r11, rdx .L2: %assign unroll 32 %assign i 0 %rep unroll vmovaps ymm1, [r9 + 32*i] vfmadd231ps ymm1, ymm2, [r10 + 32*i] vmovaps [r11 + 32*i], ymm1 %assign i i+1 %endrep add r9, 32*unroll add r10, 32*unroll add r11, 32*unroll cmp r9, rcx jne .L2 sub r8d, 1 jnz .L1 vzeroupper ret

triad_ava_asm.asm:

global triad_avx_asm_repeat ;RDI x, RSI y, RDX z, RCX n, R8 repeat pi: dd 3.14159 align 16 section .text triad_avx_asm_repeat: shl rcx, 2 add rdi, rcx add rsi, rcx add rdx, rcx vbroadcastss ymm2, [rel pi] ;neg rcx align 16 .L1: mov rax, rcx neg rax align 16 .L2: vmulps ymm1, ymm2, [rdi+rax] vaddps ymm1, ymm1, [rsi+rax] vmovaps [rdx+rax], ymm1 add rax, 32 jne .L2 sub r8d, 1 jnz .L1 vzeroupper ret global triad_avx_asm_repeat2 ;RDI x, RSI y, RDX z, RCX n, R8 repeat ;pi: dd 3.14159 align 16 section .text triad_avx_asm_repeat2: shl rcx, 2 vbroadcastss ymm2, [rel pi] align 16 .L1: xor rax, rax align 16 .L2: vmulps ymm1, ymm2, [rdi+rax] vaddps ymm1, ymm1, [rsi+rax] vmovaps [rdx+rax], ymm1 add eax, 32 cmp eax, ecx jne .L2 sub r8d, 1 jnz .L1 vzeroupper ret global triad_avx_asm_repeat_unroll16 align 16 section .text triad_avx_asm_repeat_unroll16: shl rcx, 2 add rcx, rdi vbroadcastss ymm2, [rel pi] align 16 .L1: xor rax, rax mov r9, rdi mov r10, rsi mov r11, rdx align 16 .L2: %assign unroll 16 %assign i 0 %rep unroll vmulps ymm1, ymm2, [r9 + 32*i] vaddps ymm1, ymm1, [r10 + 32*i] vmovaps [r11 + 32*i], ymm1 %assign i i+1 %endrep add r9, 32*unroll add r10, 32*unroll add r11, 32*unroll cmp r9, rcx jne .L2 sub r8d, 1 jnz .L1 vzeroupper ret

triad_sse_asm.asm:

global triad_sse_asm_repeat ;RDI x, RSI y, RDX z, RCX n, R8 repeat pi: dd 3.14159 ;align 16 section .text triad_sse_asm_repeat: shl rcx, 2 add rdi, rcx add rsi, rcx add rdx, rcx movss xmm2, [rel pi] shufps xmm2, xmm2, 0 ;neg rcx align 16 .L1: mov rax, rcx neg rax align 16 .L2: movaps xmm1, [rdi+rax] mulps xmm1, xmm2 addps xmm1, [rsi+rax] movaps [rdx+rax], xmm1 add rax, 16 jne .L2 sub r8d, 1 jnz .L1 ret global triad_sse_asm_repeat2 ;RDI x, RSI y, RDX z, RCX n, R8 repeat ;pi: dd 3.14159 ;align 16 section .text triad_sse_asm_repeat2: shl rcx, 2 movss xmm2, [rel pi] shufps xmm2, xmm2, 0 align 16 .L1: xor rax, rax align 16 .L2: movaps xmm1, [rdi+rax] mulps xmm1, xmm2 addps xmm1, [rsi+rax] movaps [rdx+rax], xmm1 add eax, 16 cmp eax, ecx jne .L2 sub r8d, 1 jnz .L1 ret global triad_sse_asm_repeat_unroll16 section .text triad_sse_asm_repeat_unroll16: shl rcx, 2 add rcx, rdi movss xmm2, [rel pi] shufps xmm2, xmm2, 0 .L1: xor rax, rax mov r9, rdi mov r10, rsi mov r11, rdx .L2: %assign unroll 8 %assign i 0 %rep unroll movaps xmm1, [r9 + 16*i] mulps xmm1, xmm2, addps xmm1, [r10 + 16*i] movaps [r11 + 16*i], xmm1 %assign i i+1 %endrep add r9, 16*unroll add r10, 16*unroll add r11, 16*unroll cmp r9, rcx jne .L2 sub r8d, 1 jnz .L1 ret


Análisis IACA

El uso de IACA revela que la fusión macro-operativa está ocurriendo y que ese no es el problema. Es Mysticial quien tiene razón: el problema es que la tienda no está utilizando el Puerto 7 en absoluto .

IACA informa lo siguiente:

Intel(R) Architecture Code Analyzer Version - 2.1 Analyzed File - ../../../tests_fma Binary Format - 64Bit Architecture - HSW Analysis Type - Throughput Throughput Analysis Report -------------------------- Block Throughput: 1.55 Cycles Throughput Bottleneck: FrontEnd, PORT2_AGU, PORT3_AGU Port Binding In Cycles Per Iteration: --------------------------------------------------------------------------------------- | Port | 0 - DV | 1 | 2 - D | 3 - D | 4 | 5 | 6 | 7 | --------------------------------------------------------------------------------------- | Cycles | 0.5 0.0 | 0.5 | 1.5 1.0 | 1.5 1.0 | 1.0 | 0.0 | 1.0 | 0.0 | --------------------------------------------------------------------------------------- N - port number or number of cycles resource conflict caused delay, DV - Divider pipe (on port 0) D - Data fetch pipe (on ports 2 and 3), CP - on a critical path F - Macro Fusion with the previous instruction occurred * - instruction micro-ops not bound to a port ^ - Micro Fusion happened # - ESP Tracking sync uop was issued @ - SSE instruction followed an AVX256 instruction, dozens of cycles penalty is expected ! - instruction not supported, was not accounted in Analysis | Num Of | Ports pressure in cycles | | | Uops | 0 - DV | 1 | 2 - D | 3 - D | 4 | 5 | 6 | 7 | | --------------------------------------------------------------------------------- | 1 | | | 1.0 1.0 | | | | | | CP | vmovaps ymm1, ymmword ptr [rdi+rax*1] | 2 | 0.5 | 0.5 | | 1.0 1.0 | | | | | CP | vfmadd231ps ymm1, ymm2, ymmword ptr [rsi+rax*1] | 2 | | | 0.5 | 0.5 | 1.0 | | | | CP | vmovaps ymmword ptr [rdx+rax*1], ymm1 | 1 | | | | | | | 1.0 | | | add rax, 0x20 | 0F | | | | | | | | | | jnz 0xffffffffffffffec Total Num Of Uops: 6

En particular, el rendimiento del bloque reportado en ciclos (1.5) coincide muy bien con una eficiencia del 66%.

Una publicación en IACA sobre este mismo fenómeno el Tue, 03/11/2014 - 12:39 fue recibida por esta respuesta de un empleado de Intel el Tue, 03/11/2014 - 23:20 :

Port7 AGU solo puede funcionar en tiendas con una dirección de memoria simple (sin registro de índice). Es por eso que el análisis anterior no muestra la utilización de port7.

Esto establece firmemente por qué el puerto 7 no se estaba utilizando.

Ahora, contrasta lo anterior con un bucle desenrollado 32x (resulta que unroll16 debería llamarse realmente unroll32 )

Intel(R) Architecture Code Analyzer Version - 2.1 Analyzed File - ../../../tests_fma Binary Format - 64Bit Architecture - HSW Analysis Type - Throughput Throughput Analysis Report -------------------------- Block Throughput: 32.00 Cycles Throughput Bottleneck: PORT2_AGU, Port2_DATA, PORT3_AGU, Port3_DATA, Port4, Port7 Port Binding In Cycles Per Iteration: --------------------------------------------------------------------------------------- | Port | 0 - DV | 1 | 2 - D | 3 - D | 4 | 5 | 6 | 7 | --------------------------------------------------------------------------------------- | Cycles | 16.0 0.0 | 16.0 | 32.0 32.0 | 32.0 32.0 | 32.0 | 2.0 | 2.0 | 32.0 | --------------------------------------------------------------------------------------- N - port number or number of cycles resource conflict caused delay, DV - Divider pipe (on port 0) D - Data fetch pipe (on ports 2 and 3), CP - on a critical path F - Macro Fusion with the previous instruction occurred * - instruction micro-ops not bound to a port ^ - Micro Fusion happened # - ESP Tracking sync uop was issued @ - SSE instruction followed an AVX256 instruction, dozens of cycles penalty is expected ! - instruction not supported, was not accounted in Analysis | Num Of | Ports pressure in cycles | | | Uops | 0 - DV | 1 | 2 - D | 3 - D | 4 | 5 | 6 | 7 | | --------------------------------------------------------------------------------- | 1 | | | 1.0 1.0 | | | | | | CP | vmovaps ymm1, ymmword ptr [r9] | 2^ | 1.0 | | | 1.0 1.0 | | | | | CP | vfmadd231ps ymm1, ymm2, ymmword ptr [r10] | 2^ | | | | | 1.0 | | | 1.0 | CP | vmovaps ymmword ptr [r11], ymm1 | 1 | | | 1.0 1.0 | | | | | | CP | vmovaps ymm1, ymmword ptr [r9+0x20] | 2^ | | 1.0 | | 1.0 1.0 | | | | | CP | vfmadd231ps ymm1, ymm2, ymmword ptr [r10+0x20] | 2^ | | | | | 1.0 | | | 1.0 | CP | vmovaps ymmword ptr [r11+0x20], ymm1 | 1 | | | 1.0 1.0 | | | | | | CP | vmovaps ymm1, ymmword ptr [r9+0x40] | 2^ | 1.0 | | | 1.0 1.0 | | | | | CP | vfmadd231ps ymm1, ymm2, ymmword ptr [r10+0x40] | 2^ | | | | | 1.0 | | | 1.0 | CP | vmovaps ymmword ptr [r11+0x40], ymm1 | 1 | | | 1.0 1.0 | | | | | | CP | vmovaps ymm1, ymmword ptr [r9+0x60] | 2^ | | 1.0 | | 1.0 1.0 | | | | | CP | vfmadd231ps ymm1, ymm2, ymmword ptr [r10+0x60] | 2^ | | | | | 1.0 | | | 1.0 | CP | vmovaps ymmword ptr [r11+0x60], ymm1 | 1 | | | 1.0 1.0 | | | | | | CP | vmovaps ymm1, ymmword ptr [r9+0x80] | 2^ | 1.0 | | | 1.0 1.0 | | | | | CP | vfmadd231ps ymm1, ymm2, ymmword ptr [r10+0x80] | 2^ | | | | | 1.0 | | | 1.0 | CP | vmovaps ymmword ptr [r11+0x80], ymm1 | 1 | | | 1.0 1.0 | | | | | | CP | vmovaps ymm1, ymmword ptr [r9+0xa0] | 2^ | | 1.0 | | 1.0 1.0 | | | | | CP | vfmadd231ps ymm1, ymm2, ymmword ptr [r10+0xa0] | 2^ | | | | | 1.0 | | | 1.0 | CP | vmovaps ymmword ptr [r11+0xa0], ymm1 | 1 | | | 1.0 1.0 | | | | | | CP | vmovaps ymm1, ymmword ptr [r9+0xc0] | 2^ | 1.0 | | | 1.0 1.0 | | | | | CP | vfmadd231ps ymm1, ymm2, ymmword ptr [r10+0xc0] | 2^ | | | | | 1.0 | | | 1.0 | CP | vmovaps ymmword ptr [r11+0xc0], ymm1 | 1 | | | 1.0 1.0 | | | | | | CP | vmovaps ymm1, ymmword ptr [r9+0xe0] | 2^ | | 1.0 | | 1.0 1.0 | | | | | CP | vfmadd231ps ymm1, ymm2, ymmword ptr [r10+0xe0] | 2^ | | | | | 1.0 | | | 1.0 | CP | vmovaps ymmword ptr [r11+0xe0], ymm1 | 1 | | | 1.0 1.0 | | | | | | CP | vmovaps ymm1, ymmword ptr [r9+0x100] | 2^ | 1.0 | | | 1.0 1.0 | | | | | CP | vfmadd231ps ymm1, ymm2, ymmword ptr [r10+0x100] | 2^ | | | | | 1.0 | | | 1.0 | CP | vmovaps ymmword ptr [r11+0x100], ymm1 | 1 | | | 1.0 1.0 | | | | | | CP | vmovaps ymm1, ymmword ptr [r9+0x120] | 2^ | | 1.0 | | 1.0 1.0 | | | | | CP | vfmadd231ps ymm1, ymm2, ymmword ptr [r10+0x120] | 2^ | | | | | 1.0 | | | 1.0 | CP | vmovaps ymmword ptr [r11+0x120], ymm1 | 1 | | | 1.0 1.0 | | | | | | CP | vmovaps ymm1, ymmword ptr [r9+0x140] | 2^ | 1.0 | | | 1.0 1.0 | | | | | CP | vfmadd231ps ymm1, ymm2, ymmword ptr [r10+0x140] | 2^ | | | | | 1.0 | | | 1.0 | CP | vmovaps ymmword ptr [r11+0x140], ymm1 | 1 | | | 1.0 1.0 | | | | | | CP | vmovaps ymm1, ymmword ptr [r9+0x160] | 2^ | | 1.0 | | 1.0 1.0 | | | | | CP | vfmadd231ps ymm1, ymm2, ymmword ptr [r10+0x160] | 2^ | | | | | 1.0 | | | 1.0 | CP | vmovaps ymmword ptr [r11+0x160], ymm1 | 1 | | | 1.0 1.0 | | | | | | CP | vmovaps ymm1, ymmword ptr [r9+0x180] | 2^ | 1.0 | | | 1.0 1.0 | | | | | CP | vfmadd231ps ymm1, ymm2, ymmword ptr [r10+0x180] | 2^ | | | | | 1.0 | | | 1.0 | CP | vmovaps ymmword ptr [r11+0x180], ymm1 | 1 | | | 1.0 1.0 | | | | | | CP | vmovaps ymm1, ymmword ptr [r9+0x1a0] | 2^ | | 1.0 | | 1.0 1.0 | | | | | CP | vfmadd231ps ymm1, ymm2, ymmword ptr [r10+0x1a0] | 2^ | | | | | 1.0 | | | 1.0 | CP | vmovaps ymmword ptr [r11+0x1a0], ymm1 | 1 | | | 1.0 1.0 | | | | | | CP | vmovaps ymm1, ymmword ptr [r9+0x1c0] | 2^ | 1.0 | | | 1.0 1.0 | | | | | CP | vfmadd231ps ymm1, ymm2, ymmword ptr [r10+0x1c0] | 2^ | | | | | 1.0 | | | 1.0 | CP | vmovaps ymmword ptr [r11+0x1c0], ymm1 | 1 | | | 1.0 1.0 | | | | | | CP | vmovaps ymm1, ymmword ptr [r9+0x1e0] | 2^ | | 1.0 | | 1.0 1.0 | | | | | CP | vfmadd231ps ymm1, ymm2, ymmword ptr [r10+0x1e0] | 2^ | | | | | 1.0 | | | 1.0 | CP | vmovaps ymmword ptr [r11+0x1e0], ymm1 | 1 | | | 1.0 1.0 | | | | | | CP | vmovaps ymm1, ymmword ptr [r9+0x200] | 2^ | 1.0 | | | 1.0 1.0 | | | | | CP | vfmadd231ps ymm1, ymm2, ymmword ptr [r10+0x200] | 2^ | | | | | 1.0 | | | 1.0 | CP | vmovaps ymmword ptr [r11+0x200], ymm1 | 1 | | | 1.0 1.0 | | | | | | CP | vmovaps ymm1, ymmword ptr [r9+0x220] | 2^ | | 1.0 | | 1.0 1.0 | | | | | CP | vfmadd231ps ymm1, ymm2, ymmword ptr [r10+0x220] | 2^ | | | | | 1.0 | | | 1.0 | CP | vmovaps ymmword ptr [r11+0x220], ymm1 | 1 | | | 1.0 1.0 | | | | | | CP | vmovaps ymm1, ymmword ptr [r9+0x240] | 2^ | 1.0 | | | 1.0 1.0 | | | | | CP | vfmadd231ps ymm1, ymm2, ymmword ptr [r10+0x240] | 2^ | | | | | 1.0 | | | 1.0 | CP | vmovaps ymmword ptr [r11+0x240], ymm1 | 1 | | | 1.0 1.0 | | | | | | CP | vmovaps ymm1, ymmword ptr [r9+0x260] | 2^ | | 1.0 | | 1.0 1.0 | | | | | CP | vfmadd231ps ymm1, ymm2, ymmword ptr [r10+0x260] | 2^ | | | | | 1.0 | | | 1.0 | CP | vmovaps ymmword ptr [r11+0x260], ymm1 | 1 | | | 1.0 1.0 | | | | | | CP | vmovaps ymm1, ymmword ptr [r9+0x280] | 2^ | 1.0 | | | 1.0 1.0 | | | | | CP | vfmadd231ps ymm1, ymm2, ymmword ptr [r10+0x280] | 2^ | | | | | 1.0 | | | 1.0 | CP | vmovaps ymmword ptr [r11+0x280], ymm1 | 1 | | | 1.0 1.0 | | | | | | CP | vmovaps ymm1, ymmword ptr [r9+0x2a0] | 2^ | | 1.0 | | 1.0 1.0 | | | | | CP | vfmadd231ps ymm1, ymm2, ymmword ptr [r10+0x2a0] | 2^ | | | | | 1.0 | | | 1.0 | CP | vmovaps ymmword ptr [r11+0x2a0], ymm1 | 1 | | | 1.0 1.0 | | | | | | CP | vmovaps ymm1, ymmword ptr [r9+0x2c0] | 2^ | 1.0 | | | 1.0 1.0 | | | | | CP | vfmadd231ps ymm1, ymm2, ymmword ptr [r10+0x2c0] | 2^ | | | | | 1.0 | | | 1.0 | CP | vmovaps ymmword ptr [r11+0x2c0], ymm1 | 1 | | | 1.0 1.0 | | | | | | CP | vmovaps ymm1, ymmword ptr [r9+0x2e0] | 2^ | | 1.0 | | 1.0 1.0 | | | | | CP | vfmadd231ps ymm1, ymm2, ymmword ptr [r10+0x2e0] | 2^ | | | | | 1.0 | | | 1.0 | CP | vmovaps ymmword ptr [r11+0x2e0], ymm1 | 1 | | | 1.0 1.0 | | | | | | CP | vmovaps ymm1, ymmword ptr [r9+0x300] | 2^ | 1.0 | | | 1.0 1.0 | | | | | CP | vfmadd231ps ymm1, ymm2, ymmword ptr [r10+0x300] | 2^ | | | | | 1.0 | | | 1.0 | CP | vmovaps ymmword ptr [r11+0x300], ymm1 | 1 | | | 1.0 1.0 | | | | | | CP | vmovaps ymm1, ymmword ptr [r9+0x320] | 2^ | | 1.0 | | 1.0 1.0 | | | | | CP | vfmadd231ps ymm1, ymm2, ymmword ptr [r10+0x320] | 2^ | | | | | 1.0 | | | 1.0 | CP | vmovaps ymmword ptr [r11+0x320], ymm1 | 1 | | | 1.0 1.0 | | | | | | CP | vmovaps ymm1, ymmword ptr [r9+0x340] | 2^ | 1.0 | | | 1.0 1.0 | | | | | CP | vfmadd231ps ymm1, ymm2, ymmword ptr [r10+0x340] | 2^ | | | | | 1.0 | | | 1.0 | CP | vmovaps ymmword ptr [r11+0x340], ymm1 | 1 | | | 1.0 1.0 | | | | | | CP | vmovaps ymm1, ymmword ptr [r9+0x360] | 2^ | | 1.0 | | 1.0 1.0 | | | | | CP | vfmadd231ps ymm1, ymm2, ymmword ptr [r10+0x360] | 2^ | | | | | 1.0 | | | 1.0 | CP | vmovaps ymmword ptr [r11+0x360], ymm1 | 1 | | | 1.0 1.0 | | | | | | CP | vmovaps ymm1, ymmword ptr [r9+0x380] | 2^ | 1.0 | | | 1.0 1.0 | | | | | CP | vfmadd231ps ymm1, ymm2, ymmword ptr [r10+0x380] | 2^ | | | | | 1.0 | | | 1.0 | CP | vmovaps ymmword ptr [r11+0x380], ymm1 | 1 | | | 1.0 1.0 | | | | | | CP | vmovaps ymm1, ymmword ptr [r9+0x3a0] | 2^ | | 1.0 | | 1.0 1.0 | | | | | CP | vfmadd231ps ymm1, ymm2, ymmword ptr [r10+0x3a0] | 2^ | | | | | 1.0 | | | 1.0 | CP | vmovaps ymmword ptr [r11+0x3a0], ymm1 | 1 | | | 1.0 1.0 | | | | | | CP | vmovaps ymm1, ymmword ptr [r9+0x3c0] | 2^ | 1.0 | | | 1.0 1.0 | | | | | CP | vfmadd231ps ymm1, ymm2, ymmword ptr [r10+0x3c0] | 2^ | | | | | 1.0 | | | 1.0 | CP | vmovaps ymmword ptr [r11+0x3c0], ymm1 | 1 | | | 1.0 1.0 | | | | | | CP | vmovaps ymm1, ymmword ptr [r9+0x3e0] | 2^ | | 1.0 | | 1.0 1.0 | | | | | CP | vfmadd231ps ymm1, ymm2, ymmword ptr [r10+0x3e0] | 2^ | | | | | 1.0 | | | 1.0 | CP | vmovaps ymmword ptr [r11+0x3e0], ymm1 | 1 | | | | | | 1.0 | | | | add r9, 0x400 | 1 | | | | | | | 1.0 | | | add r10, 0x400 | 1 | | | | | | 1.0 | | | | add r11, 0x400 | 1 | | | | | | | 1.0 | | | cmp r9, rcx | 0F | | | | | | | | | | jnz 0xfffffffffffffcaf Total Num Of Uops: 164

Vemos aquí micro-fusión y programación correcta de la tienda para el puerto 7.

Análisis manual (ver edición arriba)

Ahora puedo responder la segunda de sus preguntas: ¿Es esto posible sin desenrollar y, de ser así, cómo se puede hacer? . La respuesta es no.

Rellené las matrices x , y y z hacia la izquierda y hacia la derecha con abundante buffer para el siguiente experimento, y cambié el bucle interno a lo siguiente:

.L2: vmovaps ymm1, [rdi+rax] ; 1L vmovaps ymm0, [rsi+rax] ; 2L vmovaps [rdx+rax], ymm2 ; S1 add rax, 32 ; ADD jne .L2 ; JMP

Esto no usa FMA intencionalmente (solo cargas y almacenes) y todas las instrucciones de carga / almacenamiento no tienen dependencias, ya que, por lo tanto, no debería haber ningún peligro que impida su problema en los puertos de ejecución.

Luego probé cada permutación de la primera y segunda carga ( 1L y 2L ), la tienda ( S1 ) y la suma ( A ) mientras dejaba el salto condicional ( J ) al final, y para cada una de ellas probé todas las posibles combinación de compensaciones de x , y y z por 0 o -32 bytes (para corregir el hecho de que reordenar el add rax, 32 antes de uno de los índices r+r causaría que la carga o el almacén se dirijan a la dirección incorrecta). El bucle se alineó a 32 bytes. Las pruebas se ejecutaron en un i7-4700MQ de 2.4GHz con TurboBoost desactivado por medio de echo ''0'' > /sys/devices/system/cpu/cpufreq/boost bajo Linux, y usando 2.4 para la constante de frecuencia. Aquí están los resultados de eficiencia ( máximo de 24 ):

Cases: 0 1 2 3 4 5 6 7 L1 L2 S L1 L2 S L1 L2 S L1 L2 S L1 L2 S L1 L2 S L1 L2 S L1 L2 S -0 -0 -0 -0 -0 -32 -0 -32 -0 -0 -32 -32 -32 -0 -0 -32 -0 -32 -32 -32 -0 -32 -32 -32 ________________________________________________________________________________________________ 12SAJ: 65.34% 65.34% 49.63% 65.07% 49.70% 65.05% 49.22% 65.07% 12ASJ: 48.59% 64.48% 48.74% 49.69% 48.75% 49.69% 48.99% 48.60% 1A2SJ: 49.69% 64.77% 48.67% 64.06% 49.69% 49.69% 48.94% 49.69% 1AS2J: 48.61% 64.66% 48.73% 49.71% 48.77% 49.69% 49.05% 48.74% 1S2AJ: 49.66% 65.13% 49.49% 49.66% 48.96% 64.82% 49.02% 49.66% 1SA2J: 64.44% 64.69% 49.69% 64.34% 49.69% 64.41% 48.75% 64.14% 21SAJ: 65.33%* 65.34% 49.70% 65.06% 49.62% 65.07% 49.22% 65.04% 21ASJ: Hypothetically =12ASJ 2A1SJ: Hypothetically =1A2SJ 2AS1J: Hypothetically =1AS2J 2S1AJ: Hypothetically =1S2AJ 2SA1J: Hypothetically =1SA2J S21AJ: 48.91% 65.19% 49.04% 49.72% 49.12% 49.63% 49.21% 48.95% S2A1J: Hypothetically =S1A2J SA21J: Hypothetically =SA12J SA12J: 64.69% 64.93% 49.70% 64.66% 49.69% 64.27% 48.71% 64.56% S12AJ: 48.90% 65.20% 49.12% 49.63% 49.03% 49.70% 49.21%* 48.94% S1A2J: 49.69% 64.74% 48.65% 64.48% 49.43% 49.69% 48.66% 49.69% A2S1J: Hypothetically =A1S2J A21SJ: Hypothetically =A12SJ A12SJ: 64.62% 64.45% 49.69% 64.57% 49.69% 64.45% 48.58% 63.99% A1S2J: 49.72% 64.69% 49.72% 49.72% 48.67% 64.46% 48.95% 49.72% AS21J: Hypothetically =AS21J AS12J: 48.71% 64.53% 48.76% 49.69% 48.76% 49.74% 48.93% 48.69%

Podemos notar algunas cosas de la tabla:

  • Varias mesetas de resultados, pero solo dos principales: poco menos del 50% y alrededor del 65%.
  • L1 y L2 pueden permutar libremente entre sí sin afectar el resultado.
  • La compensación de los accesos en -32 bytes puede cambiar la eficiencia.
  • Los patrones que nos interesan (Cargar 1, Cargar 2, Almacenar 1 y Saltar con Agregar en cualquier lugar a su alrededor y las compensaciones de -32 aplicadas correctamente) son todos iguales y todos en la meseta más alta:
    • 12SAJ Caso 0 (sin compensaciones aplicadas), con eficiencia 65.34% (la más alta)
    • 12ASJ Caso 1 ( S-32 ), con eficiencia 64.48%
    • 1A2SJ Case 3 ( 2L-32 , S-32 ), con eficiencia 64.06%
    • A12SJ Case 7 ( 1L-32 , 2L-32 , S-32 ), con eficiencia 63.99%
  • Siempre existe al menos un "caso" para cada permutación que permite la ejecución en la meseta más alta de eficiencia. En particular, el Caso 1 (donde S-32 ) parece garantizar esto.
  • Los casos 2, 4 y 6 garantizan la ejecución en la meseta inferior. Tienen en común que una o ambas cargas están compensadas por -32 mientras que la tienda no lo está.
  • Para los casos 0, 3, 5 y 7, depende de la permutación.

De donde podemos sacar al menos algunas conclusiones:

  • A los puertos de ejecución 2 y 3 realmente no les importa desde qué dirección de carga generan y desde dónde se cargan.
  • La fusión macro-op de add y jmp parece no verse afectada por ninguna permutación de las instrucciones (en particular bajo la compensación del caso 1), lo que me hace creer que la conclusión de @Evgeny Kluev es incorrecta: la distancia de add desde jne no parece impactar su fusión. Estoy razonablemente seguro ahora que el Haswell ROB maneja esto correctamente.
    • Lo que Evgeny estaba viendo ( 12SAJ de 12SAJ con eficiencia 65% a los otros con eficiencia 49% dentro del Caso 0) fue un efecto debido únicamente al valor de las direcciones cargadas y almacenadas, y no debido a la incapacidad del núcleo para macro -Fusionar el complemento y la rama.
    • Además, la fusión macro-op debe ocurrir al menos parte del tiempo , ya que el tiempo de bucle promedio es de 1.5 CC. Si no se produjera la fusión macro-op, esto sería un mínimo de 2CC.
  • Después de haber probado todas las permutaciones de instrucciones válidas e inválidas dentro del ciclo no desenrollado, no hemos visto nada superior al 65,34%. Esto responde empíricamente con un "no" a la pregunta de si es posible usar todo el ancho de banda sin desenrollar.

Hipotetizaré varias explicaciones posibles:

  • Estamos viendo una perversión extraña debido al valor de las direcciones entre sí.
    • Si es así, existiría un conjunto de compensaciones de x , y y z que permitirían un rendimiento máximo. Las pruebas aleatorias rápidas de mi parte no parecen apoyar esto.
  • Estamos viendo el ciclo correr en modo de uno o dos pasos; Las iteraciones del bucle se alternan en un ciclo de reloj, luego en dos.

    • Esto podría ser la fusión macro-operatoria afectada por los decodificadores. De Agner Fog:

      Las instrucciones aritméticas / lógicas fusionables no se pueden decodificar en el último de los cuatro decodificadores en los procesadores Sandy Bridge e Ivy Bridge. No he probado si esto también se aplica a Haswell.

    • Alternativamente, cada dos ciclos de reloj se emite una instrucción al puerto "incorrecto", bloqueando la siguiente iteración para un ciclo de reloj adicional. Tal situación se autocorregiría en el siguiente ciclo de reloj, pero seguiría siendo oscilatoria.
      • Si alguien tiene acceso a los contadores de rendimiento de Intel, debe mirar los eventos UOPS_EXECUTED_PORT.PORT_[0-7] . Si no se produce oscilación, todos los puertos que se usen se vincularán por igual durante el período de tiempo relevante; De lo contrario, si se produce una oscilación, habrá una división del 50%. Especialmente importante es mirar los puertos que Mystical señaló (0, 1, 6 y 7).

Y esto es lo que creo que no está sucediendo:

  • No creo que la aritmética fusionada + rama uop esté bloqueando la ejecución yendo al puerto 0, ya que las ramas tomadas predichas se envían exclusivamente al puerto 6 (consulte las Tablas de instrucciones de Agner Fog en Haswell -> Control transfer instructions ). Después de algunas iteraciones del bucle anterior, el predictor de rama aprenderá que esta rama es un bucle y siempre pronosticará como tomado.

Creo que este es un problema que se resolverá con los contadores de rendimiento de Intel.