También depende de la combinación de instrucciones. Su procesador tendrá varias unidades de cómputo disponibles en cualquier momento, y obtendrá el máximo rendimiento si todas están llenas todo el tiempo. Entonces, ejecutar un bucle de mul es tan rápido como ejecutar un bucle o agregar, pero no ocurre lo mismo si la expresión se vuelve más compleja.
Por ejemplo, tome este bucle:
for(int j=0;j<NUMITER;j++) {
for(int i=1;i<NUMEL;i++) {
bla += 2.1 + arr1[i] + arr2[i] + arr3[i] + arr4[i] ;
}
}
para NUMITER = 10^7, Numel = 10^2, ambas matrices inicializadas a pequeños números positivos (NaN es mucho más lento), esto se lleva a 6,0 segundos utilizando se duplica en un proceso de 64 bits Si sustituyo el lazo con
bla += 2.1 * arr1[i] + arr2[i] + arr3[i] * arr4[i] ;
Sólo se tarda 1,7 segundos ... así ya que "fue la mano" las adiciones, las mul eran esencialmente libre; y la reducción en las adiciones ayudó. Se consigue de más confuso:
bla += 2.1 + arr1[i] * arr2[i] + arr3[i] * arr4[i] ;
- misma mul/añadir distribución, pero ahora se añade la constante en lugar de multiplicarse en - toma 3,7 segundos. Es probable que su procesador esté optimizado para realizar cálculos numéricos típicos de manera más eficiente; así que las sumas de mull y las sumas escaladas son casi tan buenas como pueden ser; la adición de constantes no es tan común, por lo que es más lento ...
bla += someval + arr1[i] * arr2[i] + arr3[i] * arr4[i] ; /*someval == 2.1*/
toma de nuevo 1.7 segundos.
bla += someval + arr1[i] + arr2[i] + arr3[i] + arr4[i] ; /*someval == 2.1*/
(igual que el ciclo inicial, pero sin la constante adición costosa: 2.1 segundo)
bla += someval * arr1[i] * arr2[i] * arr3[i] * arr4[i] ; /*someval == 2.1*/
(en su mayoría MULS, pero una adición: 1,9 segundos)
Así que, básicamente; es difícil decir cuál es más rápido, pero si desea evitar los cuellos de botella, lo más importante es tener una mezcla sensata, evitar NaN o INF, evitar agregar constantes. Hagas lo que hagas, asegúrate de probar y probar varias configuraciones del compilador, ya que a menudo los pequeños cambios pueden marcar la diferencia.
Algunos más casos:
bla *= someval; // someval very near 1.0; takes 2.1 seconds
bla *= arr1[i] ;// arr1[i] all very near 1.0; takes 66(!) seconds
bla += someval + arr1[i] * arr2[i] + arr3[i] * arr4[i] ; // 1.6 seconds
bla += someval + arr1[i] * arr2[i] + arr3[i] * arr4[i] ; //32-bit mode, 2.2 seconds
bla += someval + arr1[i] * arr2[i] + arr3[i] * arr4[i] ; //32-bit mode, floats 2.2 seconds
bla += someval * arr1[i]* arr2[i];// 0.9 in x64, 1.6 in x86
bla += someval * arr1[i];// 0.55 in x64, 0.8 in x86
bla += arr1[i] * arr2[i];// 0.8 in x64, 0.8 in x86, 0.95 in CLR+x64, 0.8 in CLR+x86
La mezcla de instrucciones es un buen punto, tengo personas con las que trabajo que insisten en que un DSP de punto flotante 200 va a realizar un DSP de 600 puntos fijos. No hacen absolutamente ningún procesamiento de bucle cerrado, y pasan más tiempo procesando E/S que realizando calcuaciones. Un procesador de punto fijo más rápido ganaría según la combinación general de instrucciones, pero la gente simplemente piensa que las unidades de FP son mágicas en lugar de una implementación de HW de una estructura de datos. – NoMoreZealots
Ah sí, el appproach mágico ;-) - eso es desafortunado. –
buena explicación con ejemplos intuitivos! –