Optimice para una multiplicación rápida pero lenta: FMA y doubledouble

Cuando obtuve por primera vez un procesador Haswell, traté de implementar FMA para determinar el conjunto de Mandelbrot. El algoritmo principal es este:

intn = 0; for(int32_t i=0; i<maxiter; i++) { floatn x2 = square(x), y2 = square(y); //square(x) = x*x floatn r2 = x2 + y2; booln mask = r2<cut; //booln is in the float domain non integer domain if(!horizontal_or(mask)) break; //_mm256_testz_pd(mask) n -= mask floatn t = x*y; mul2(t); //mul2(t): t*=2 x = x2 - y2 + cx; y = t + cy; } 

Esto determina si n píxeles están en el conjunto de Mandelbrot. Por lo tanto, para el punto flotante doble se ejecuta en 4 píxeles ( floatn = __m256d , intn = __m256i ). Esto requiere 4 multiplicaciones de coma flotante SIMD y cuatro adiciones de coma flotante SIMD.

Luego modifiqué esto para trabajar con FMA como este

 intn n = 0; for(int32_t i=0; i<maxiter; i++) { floatn r2 = mul_add(x,x,y*y); booln mask = r2<cut; if(!horizontal_or(mask)) break; add_mask(n,mask); floatn t = x*y; x = mul_sub(x,x, mul_sub(y,y,cx)); y = mul_add(2.0f,t,cy); } 

donde mul_add llama a _mm256_fmad_pd y mul_sub llama _mm256_fmsub_pd . Este método usa 4 operaciones FMA SIMD y dos multiplicaciones SIMD, que son dos operaciones aritméticas menos y luego sin FMA. Además, FMA y la multiplicación pueden usar dos puertos y agregar solo uno.

Para que mis pruebas fueran menos tendenciosas, amplié una región que está completamente en el conjunto de Mandelbrot, por lo que todos los valores son maxiter . En este caso, el método que utiliza FMA es aproximadamente un 27% más rápido. Esa es sin duda una mejora, pero pasar de SSE a AVX duplicó mi rendimiento, así que esperaba otro factor de dos con FMA.

Pero luego encontré esta respuesta con respecto a FMA donde dice

El aspecto importante de la instrucción fusionar-multiplicar-agregar es la precisión (virtualmente) infinita del resultado intermedio. Esto ayuda con el rendimiento, pero no tanto porque dos operaciones están codificadas en una sola instrucción: ayuda con el rendimiento porque la precisión prácticamente infinita del resultado intermedio es a veces importante y muy costosa de recuperar con la multiplicación y adición ordinarias cuando este nivel de la precisión es realmente lo que busca el progtwigdor.

y luego da un ejemplo de multiplicación doble * doble a doble-doble

 high = a * b; /* double-precision approximation of the real product */ low = fma(a, b, -high); /* remainder of the real product */ 

A partir de esto, concluí que estaba implementando FMA de manera no óptima, así que decidí implementar SIMD doble-doble. Implementé el doble-doble basado en el papel Números de coma flotante de precisión extendida para el cálculo de GPU . El papel es para doble flotación, así que lo modifiqué para doble doble. Además, en lugar de empaquetar un valor doble-doble en un registro SIMD, empaqueté 4 valores doble-doble en un solo registro AVX alto y un registro bajo AVX.

Para el conjunto de Mandelbrot, lo que realmente necesito es una multiplicación y adición de doble doble. En ese documento, estas son las funciones df64_add y df64_mult . La imagen a continuación muestra el ensamblaje para mi función df64_mult para el software FMA (izquierda) y el hardware FMA (derecha). Esto muestra claramente que el hardware FMA es una gran mejora para la multiplicación de doble doble.

software fma vs hardware

Entonces, ¿cómo funciona el hardware FMA en el cálculo del doble de Mandelbrot? La respuesta es que es solo un 15% más rápido que con el software FMA. Eso es mucho menos de lo que esperaba. El cálculo doble-doble de Mandelbrot necesita 4 adiciones dobles-dobles y cuatro multiplicaciones dobles-dobles ( x*x , y*y , x*y , y 2*(x*y) ). Sin embargo, la multiplicación 2*(x*y) es trivial para doble-doble por lo que esta multiplicación se puede ignorar en el costo. Por lo tanto, la razón por la que creo que la mejora con hardware FMA es tan pequeña es que el cálculo está dominado por la adición lenta de doble doble (consulte el ensamblaje a continuación).

Solía ​​ser que la multiplicación era más lenta que la sum (y los progtwigdores usaban varios trucos para evitar la multiplicación) pero con Haswell parece que es al revés. No solo por FMA sino también porque la multiplicación puede usar dos puertos, pero solo uno.

Entonces mis preguntas (finalmente) son:

  1. ¿Cómo se optimiza cuando la adición es lenta en comparación con la multiplicación?
  2. ¿Hay alguna manera algebraica de cambiar mi algoritmo para usar más multiplicaciones y menos adiciones? Sé que hay un método para hacer lo contrario, por ejemplo, (x+y)*(x+y) - (x*x+y*y) = 2*x*y que usan dos adiciones más para una multiplicación menos.
  3. ¿Hay alguna forma de simplemente la función df64_add (por ejemplo, usando FMA)?

En caso de que alguien se pregunte, el método del doble doble es aproximadamente diez veces más lento que el doble. Eso no es tan malo, creo que si hubiera un tipo de hardware de cuádruple precisión, sería al menos dos veces más lento que el doble, así que mi método de software es unas cinco veces más lento de lo que esperaría para el hardware si existiera.

df64_add assembly

 vmovapd 8(%rsp), %ymm0 movq %rdi, %rax vmovapd 72(%rsp), %ymm1 vmovapd 40(%rsp), %ymm3 vaddpd %ymm1, %ymm0, %ymm4 vmovapd 104(%rsp), %ymm5 vsubpd %ymm0, %ymm4, %ymm2 vsubpd %ymm2, %ymm1, %ymm1 vsubpd %ymm2, %ymm4, %ymm2 vsubpd %ymm2, %ymm0, %ymm0 vaddpd %ymm1, %ymm0, %ymm2 vaddpd %ymm5, %ymm3, %ymm1 vsubpd %ymm3, %ymm1, %ymm6 vsubpd %ymm6, %ymm5, %ymm5 vsubpd %ymm6, %ymm1, %ymm6 vaddpd %ymm1, %ymm2, %ymm1 vsubpd %ymm6, %ymm3, %ymm3 vaddpd %ymm1, %ymm4, %ymm2 vaddpd %ymm5, %ymm3, %ymm3 vsubpd %ymm4, %ymm2, %ymm4 vsubpd %ymm4, %ymm1, %ymm1 vaddpd %ymm3, %ymm1, %ymm0 vaddpd %ymm0, %ymm2, %ymm1 vsubpd %ymm2, %ymm1, %ymm2 vmovapd %ymm1, (%rdi) vsubpd %ymm2, %ymm0, %ymm0 vmovapd %ymm0, 32(%rdi) vzeroupper ret 

Para responder a mi tercera pregunta, encontré una solución más rápida para la adición de doble doble. Encontré una definición alternativa en el documento Implementación de operadores de flotación flotante en hardware de gráficos .

 Theorem 5 (Add22 theorem) Let be ah+al and bh+bl the float-float arguments of the following algorithm: Add22 (ah ,al ,bh ,bl) 1 r = ah ⊕ bh 2 if | ah | ≥ | bh | then 3 s = ((( ah ⊖ r ) ⊕ bh ) ⊕ bl ) ⊕ al 4 else 5 s = ((( bh ⊖ r ) ⊕ ah ) ⊕ al ) ⊕ bl 6 ( rh , rl ) = add12 ( r , s ) 7 return (rh , rl) 

Aquí es cómo lo implementé (pseudo-código):

 static inline doubledoublen add22(doubledoublen const &a, doubledouble const &b) { doublen aa,ab,ah,bh,al,bl; booln mask; aa = abs(a.hi); //_mm256_and_pd ab = abs(b.hi); mask = aa >= ab; //_mm256_cmple_pd // z = select(cut,x,y) is a SIMD version of z = cut ? x : y; ah = select(mask,a.hi,b.hi); //_mm256_blendv_pd bh = select(mask,b.hi,a.hi); al = select(mask,a.lo,b.lo); bl = select(mask,b.lo,a.lo); doublen r, s; r = ah + bh; s = (((ah - r) + bh) + bl ) + al; return two_sum(r,s); } 

Esta definición de Add22 usa 11 adiciones en lugar de 20, pero requiere un código adicional para determinar si |ah| >= |bh| |ah| >= |bh| . Aquí hay una discusión sobre cómo implementar las funciones SIMD minmag y maxmag . Afortunadamente, la mayoría del código adicional no usa el puerto 1. Ahora solo 12 instrucciones van al puerto 1 en lugar de 20.

Aquí hay un análisis de rendimiento de IACA para el nuevo Add22

 Throughput Analysis Report -------------------------- Block Throughput: 12.05 Cycles Throughput Bottleneck: Port1 Port Binding In Cycles Per Iteration: --------------------------------------------------------------------------------------- | Port | 0 - DV | 1 | 2 - D | 3 - D | 4 | 5 | 6 | 7 | --------------------------------------------------------------------------------------- | Cycles | 0.0 0.0 | 12.0 | 2.5 2.5 | 2.5 2.5 | 2.0 | 10.0 | 0.0 | 2.0 | --------------------------------------------------------------------------------------- | Num Of | Ports pressure in cycles | | | Uops | 0 - DV | 1 | 2 - D | 3 - D | 4 | 5 | 6 | 7 | | --------------------------------------------------------------------------------- | 1 | | | 0.5 0.5 | 0.5 0.5 | | | | | | vmovapd ymm3, ymmword ptr [rip] | 1 | | | 0.5 0.5 | 0.5 0.5 | | | | | | vmovapd ymm0, ymmword ptr [rdx] | 1 | | | 0.5 0.5 | 0.5 0.5 | | | | | | vmovapd ymm4, ymmword ptr [rsi] | 1 | | | | | | 1.0 | | | | vandpd ymm2, ymm4, ymm3 | 1 | | | | | | 1.0 | | | | vandpd ymm3, ymm0, ymm3 | 1 | | 1.0 | | | | | | | CP | vcmppd ymm2, ymm3, ymm2, 0x2 | 1 | | | 0.5 0.5 | 0.5 0.5 | | | | | | vmovapd ymm3, ymmword ptr [rsi+0x20] | 2 | | | | | | 2.0 | | | | vblendvpd ymm1, ymm0, ymm4, ymm2 | 2 | | | | | | 2.0 | | | | vblendvpd ymm4, ymm4, ymm0, ymm2 | 1 | | | 0.5 0.5 | 0.5 0.5 | | | | | | vmovapd ymm0, ymmword ptr [rdx+0x20] | 2 | | | | | | 2.0 | | | | vblendvpd ymm5, ymm0, ymm3, ymm2 | 2 | | | | | | 2.0 | | | | vblendvpd ymm0, ymm3, ymm0, ymm2 | 1 | | 1.0 | | | | | | | CP | vaddpd ymm3, ymm1, ymm4 | 1 | | 1.0 | | | | | | | CP | vsubpd ymm2, ymm1, ymm3 | 1 | | 1.0 | | | | | | | CP | vaddpd ymm1, ymm2, ymm4 | 1 | | 1.0 | | | | | | | CP | vaddpd ymm1, ymm1, ymm0 | 1 | | 1.0 | | | | | | | CP | vaddpd ymm0, ymm1, ymm5 | 1 | | 1.0 | | | | | | | CP | vaddpd ymm2, ymm3, ymm0 | 1 | | 1.0 | | | | | | | CP | vsubpd ymm1, ymm2, ymm3 | 2^ | | | | | 1.0 | | | 1.0 | | vmovapd ymmword ptr [rdi], ymm2 | 1 | | 1.0 | | | | | | | CP | vsubpd ymm0, ymm0, ymm1 | 1 | | 1.0 | | | | | | | CP | vsubpd ymm1, ymm2, ymm1 | 1 | | 1.0 | | | | | | | CP | vsubpd ymm3, ymm3, ymm1 | 1 | | 1.0 | | | | | | | CP | vaddpd ymm0, ymm3, ymm0 | 2^ | | | | | 1.0 | | | 1.0 | | vmovapd ymmword ptr [rdi+0x20], ymm0 

y aquí está el análisis del rendimiento del viejo

 Throughput Analysis Report -------------------------- Block Throughput: 20.00 Cycles Throughput Bottleneck: Port1 Port Binding In Cycles Per Iteration: --------------------------------------------------------------------------------------- | Port | 0 - DV | 1 | 2 - D | 3 - D | 4 | 5 | 6 | 7 | --------------------------------------------------------------------------------------- | Cycles | 0.0 0.0 | 20.0 | 2.0 2.0 | 2.0 2.0 | 2.0 | 0.0 | 0.0 | 2.0 | --------------------------------------------------------------------------------------- | Num Of | Ports pressure in cycles | | | Uops | 0 - DV | 1 | 2 - D | 3 - D | 4 | 5 | 6 | 7 | | --------------------------------------------------------------------------------- | 1 | | | 1.0 1.0 | | | | | | | vmovapd ymm0, ymmword ptr [rsi] | 1 | | | | 1.0 1.0 | | | | | | vmovapd ymm1, ymmword ptr [rdx] | 1 | | | 1.0 1.0 | | | | | | | vmovapd ymm3, ymmword ptr [rsi+0x20] | 1 | | 1.0 | | | | | | | CP | vaddpd ymm4, ymm0, ymm1 | 1 | | | | 1.0 1.0 | | | | | | vmovapd ymm5, ymmword ptr [rdx+0x20] | 1 | | 1.0 | | | | | | | CP | vsubpd ymm2, ymm4, ymm0 | 1 | | 1.0 | | | | | | | CP | vsubpd ymm1, ymm1, ymm2 | 1 | | 1.0 | | | | | | | CP | vsubpd ymm2, ymm4, ymm2 | 1 | | 1.0 | | | | | | | CP | vsubpd ymm0, ymm0, ymm2 | 1 | | 1.0 | | | | | | | CP | vaddpd ymm2, ymm0, ymm1 | 1 | | 1.0 | | | | | | | CP | vaddpd ymm1, ymm3, ymm5 | 1 | | 1.0 | | | | | | | CP | vsubpd ymm6, ymm1, ymm3 | 1 | | 1.0 | | | | | | | CP | vsubpd ymm5, ymm5, ymm6 | 1 | | 1.0 | | | | | | | CP | vsubpd ymm6, ymm1, ymm6 | 1 | | 1.0 | | | | | | | CP | vaddpd ymm1, ymm2, ymm1 | 1 | | 1.0 | | | | | | | CP | vsubpd ymm3, ymm3, ymm6 | 1 | | 1.0 | | | | | | | CP | vaddpd ymm2, ymm4, ymm1 | 1 | | 1.0 | | | | | | | CP | vaddpd ymm3, ymm3, ymm5 | 1 | | 1.0 | | | | | | | CP | vsubpd ymm4, ymm2, ymm4 | 1 | | 1.0 | | | | | | | CP | vsubpd ymm1, ymm1, ymm4 | 1 | | 1.0 | | | | | | | CP | vaddpd ymm0, ymm1, ymm3 | 1 | | 1.0 | | | | | | | CP | vaddpd ymm1, ymm2, ymm0 | 1 | | 1.0 | | | | | | | CP | vsubpd ymm2, ymm1, ymm2 | 2^ | | | | | 1.0 | | | 1.0 | | vmovapd ymmword ptr [rdi], ymm1 | 1 | | 1.0 | | | | | | | CP | vsubpd ymm0, ymm0, ymm2 | 2^ | | | | | 1.0 | | | 1.0 | | vmovapd ymmword ptr [rdi+0x20], ymm0 

Una mejor solución sería si hubiera tres instrucciones de modo de redondeo único de operando además de FMA. Me parece que debería haber instrucciones de modo de redondeo único para

 a + b + c a * b + c //FMA - this is the only one in x86 so far a * b * c 

Para acelerar el algoritmo, utilizo una versión simplificada basada en 2 fma, 1 mul y 2 add. Proceso 8 iteraciones de esa manera. Luego calcule el radio de escape y deshaga las últimas 8 iteraciones si es necesario.

El siguiente ciclo crítico X = X ^ 2 + C escrito con intrínsecos x86 está muy bien desenrollado por el comstackdor, y usted detectará después de desenrollar que las 2 operaciones de FMA no dependen mucho la una de la otra.

 // IACA_START; for (j = 0; j < 8; j++) { Xrm = _mm256_mul_ps(Xre, Xim); Xtt = _mm256_fmsub_ps(Xim, Xim, Cre); Xrm = _mm256_add_ps(Xrm, Xrm); Xim = _mm256_add_ps(Cim, Xrm); Xre = _mm256_fmsub_ps(Xre, Xre, Xtt); } // for // IACA_END; 

Y luego calculo el radio de escape (| X |

 cmp = _mm256_mul_ps(Xre, Xre); cmp = _mm256_fmadd_ps(Xim, Xim, cmp); cmp = _mm256_cmp_ps(cmp, vec_threshold, _CMP_LE_OS); if (_mm256_testc_si256((__m256i) cmp, vec_one)) { i += 8; continue; } 

Mencionas "la adición es lenta", esto no es exactamente cierto, pero tienes razón, el rendimiento de la multiplicación aumenta y aumenta con el tiempo en las architectures recientes.

Las latencias y dependencias de multiplicación son la clave. FMA tiene un rendimiento de 1 ciclo y una latencia de 5 ciclos. la ejecución de instrucciones FMA independientes puede superponerse.

Las adiciones basadas en el resultado de una multiplicación obtienen el golpe de latencia completa.

Por lo tanto, debe romper estas dependencias inmediatas haciendo "puntadas de código" y calcular 2 puntos en el mismo ciclo, y simplemente intercalar el código antes de verificar con IACA qué sucederá. El siguiente código tiene 2 conjuntos de variables (con el sufijo 0 y 1 para X0 = X0 ^ 2 + C0, X1 = X1 ^ 2 + C1) y comienza a llenar los agujeros de FMA

 for (j = 0; j < 8; j++) { Xrm0 = _mm256_mul_ps(Xre0, Xim0); Xrm1 = _mm256_mul_ps(Xre1, Xim1); Xtt0 = _mm256_fmsub_ps(Xim0, Xim0, Cre); Xtt1 = _mm256_fmsub_ps(Xim1, Xim1, Cre); Xrm0 = _mm256_add_ps(Xrm0, Xrm0); Xrm1 = _mm256_add_ps(Xrm1, Xrm1); Xim0 = _mm256_add_ps(Cim0, Xrm0); Xim1 = _mm256_add_ps(Cim1, Xrm1); Xre0 = _mm256_fmsub_ps(Xre0, Xre0, Xtt0); Xre1 = _mm256_fmsub_ps(Xre1, Xre1, Xtt1); } // for 

Para resumir,

  • puedes dividir a la mitad el número de instrucciones en tu ciclo crítico
  • puede agregar más instrucciones independientes y aprovechar el alto rendimiento frente a la baja latencia de las multiplicaciones y fusionar multiplicar y agregar.

Usted menciona el siguiente código:

 vsubpd %ymm0, %ymm4, %ymm2 vsubpd %ymm2, %ymm1, %ymm1 < -- immediate dependency ymm2 vsubpd %ymm2, %ymm4, %ymm2 vsubpd %ymm2, %ymm0, %ymm0 <-- immediate dependency ymm2 vaddpd %ymm1, %ymm0, %ymm2 <-- immediate dependency ymm0 vaddpd %ymm5, %ymm3, %ymm1 vsubpd %ymm3, %ymm1, %ymm6 <-- immediate dependency ymm1 vsubpd %ymm6, %ymm5, %ymm5 <-- immediate dependency ymm6 vsubpd %ymm6, %ymm1, %ymm6 <-- dependency ymm1, ymm6 vaddpd %ymm1, %ymm2, %ymm1 vsubpd %ymm6, %ymm3, %ymm3 <-- dependency ymm6 vaddpd %ymm1, %ymm4, %ymm2 vaddpd %ymm5, %ymm3, %ymm3 <-- dependency ymm3 vsubpd %ymm4, %ymm2, %ymm4 vsubpd %ymm4, %ymm1, %ymm1 <-- immediate dependency ymm4 vaddpd %ymm3, %ymm1, %ymm0 <-- immediate dependency ymm1, ymm3 vaddpd %ymm0, %ymm2, %ymm1 <-- immediate dependency ymm0 vsubpd %ymm2, %ymm1, %ymm2 <-- immediate dependency ymm1 

Si lo comprueba cuidadosamente, se trata en su mayoría de operaciones dependientes, y no se cumple una regla básica sobre la latencia / eficiencia de rendimiento. La mayoría de las instrucciones dependen del resultado de la anterior o de 2 instrucciones anteriores. Esta secuencia contiene una ruta crítica de 30 ciclos (alrededor de 9 o 10 instrucciones sobre "3 ciclos de latencia" / "1 ciclo de rendimiento").

Su IACA informa "CP" => instrucción en la ruta crítica, y el costo evaluado es de 20 ciclos de rendimiento. Debería obtener el informe de latencia porque es el que importa si está interesado en la velocidad de ejecución.

Para eliminar el costo de esta ruta crítica, debe intercalar aproximadamente 20 instrucciones más similares si el comstackdor no puede hacerlo (p. Ej., Porque su código doble doble está en una biblioteca separada comstackda sin optimizaciones -flto y vzeroupper en todas partes en la entrada y salida de funciones , el vectorizador solo funciona bien con el código en línea).

Una posibilidad es ejecutar 2 cálculos en paralelo (ver cómo se borda el código en una publicación anterior para mejorar el pipeline)

Si asumo que su código doble-doble se parece a esta implementación "estándar"

 // (r,e) = x + y #define two_sum(x, y, r, e) do { double t; r = x + y; t = r - x; e = (x - (r - t)) + (y - t); } while (0) #define two_difference(x, y, r, e) \ do { double t; r = x - y; t = r - x; e = (x - (r - t)) - (y + t); } while (0) ..... 

Luego debe considerar el siguiente código, donde las instrucciones se entrelazan en un grano bastante fino.

 // (r1, e1) = x1 + y1, (r2, e2) x2 + y2 #define two_sum(x1, y1, x2, y2, r1, e1, r2, e2) do { double t1, t2 \ r1 = x1 + y1; r2 = x2 + y2; \ t1 = r1 - x1; t2 = r2 - x2; \ e1 = (x1 - (r1 - t1)) + (y1 - t1); e2 = (x2 - (r2 - t2)) + (y2 - t2); \ } while (0) .... 

Luego, esto crea un código como el siguiente (aproximadamente la misma ruta crítica en un informe de latencia y aproximadamente 35 instrucciones). Para obtener detalles sobre el tiempo de ejecución, la ejecución fuera de orden debe sobrevolar sin detenerse.

 vsubsd %xmm2, %xmm0, %xmm8 vsubsd %xmm3, %xmm1, %xmm1 vaddsd %xmm4, %xmm4, %xmm4 vaddsd %xmm5, %xmm5, %xmm5 vsubsd %xmm0, %xmm8, %xmm9 vsubsd %xmm9, %xmm8, %xmm10 vaddsd %xmm2, %xmm9, %xmm2 vsubsd %xmm10, %xmm0, %xmm0 vsubsd %xmm2, %xmm0, %xmm11 vaddsd %xmm14, %xmm4, %xmm2 vaddsd %xmm11, %xmm1, %xmm12 vsubsd %xmm4, %xmm2, %xmm0 vaddsd %xmm12, %xmm8, %xmm13 vsubsd %xmm0, %xmm2, %xmm11 vsubsd %xmm0, %xmm14, %xmm1 vaddsd %xmm6, %xmm13, %xmm3 vsubsd %xmm8, %xmm13, %xmm8 vsubsd %xmm11, %xmm4, %xmm4 vsubsd %xmm13, %xmm3, %xmm15 vsubsd %xmm8, %xmm12, %xmm12 vaddsd %xmm1, %xmm4, %xmm14 vsubsd %xmm15, %xmm3, %xmm9 vsubsd %xmm15, %xmm6, %xmm6 vaddsd %xmm7, %xmm12, %xmm7 vsubsd %xmm9, %xmm13, %xmm10 vaddsd 16(%rsp), %xmm5, %xmm9 vaddsd %xmm6, %xmm10, %xmm15 vaddsd %xmm14, %xmm9, %xmm10 vaddsd %xmm15, %xmm7, %xmm13 vaddsd %xmm10, %xmm2, %xmm15 vaddsd %xmm13, %xmm3, %xmm6 vsubsd %xmm2, %xmm15, %xmm2 vsubsd %xmm3, %xmm6, %xmm3 vsubsd %xmm2, %xmm10, %xmm11 vsubsd %xmm3, %xmm13, %xmm0 

Resumen:

  • incorpore su código fuente doble-doble: el comstackdor y el vectorizador no pueden optimizar a través de las llamadas de función debido a las limitaciones ABI, y a través de los accesos a la memoria por temor a aliasing.

  • código de puntada para equilibrar el rendimiento y la latencia y maximizar el uso de los puertos de la CPU (y también maximizar las instrucciones por ciclo), siempre que el comstackdor no derrame demasiados registros en la memoria.

Puede realizar un seguimiento de los impactos de optimización con la utilidad perf (paquetes linux-tools-generic y linux-cloud-tools-generic) para obtener el número de instrucciones ejecutadas y el número de instrucciones por ciclo.