TL; DR: ¿Por qué los datos de multiplicación/fundición en size_t
son lentos y por qué esto varía según la plataforma?Rendimiento del elenco de size_t al doble
Tengo algunos problemas de rendimiento que no entiendo del todo. El contexto es un capturador de fotogramas de la cámara donde se lee y postprocesa una imagen de 128x128 uint16_t a una velocidad de varios 100 Hz.
En el post-tratamiento de I generan un histograma frame->histo
que es de uint32_t
y tiene thismaxval
= 2^16 elementos, básicamente I Flirteo con todos los valores de intensidad. El uso de este histograma puedo calcular la suma y la suma al cuadrado:
double sum=0, sumsquared=0;
size_t thismaxval = 1 << 16;
for(size_t i = 0; i < thismaxval; i++) {
sum += (double)i * frame->histo[i];
sumsquared += (double)(i * i) * frame->histo[i];
}
perfilar el código con el perfil Me lo siguiente (muestras, porcentaje, el código):
58228 32.1263 : sum += (double)i * frame->histo[i];
116760 64.4204 : sumsquared += (double)(i * i) * frame->histo[i];
o, la primera línea de toma 32 % del tiempo de CPU, la segunda línea 64%.
Hice algunos benchmarking y parece ser el tipo de datos/el molde que es problemático. Cuando cambio el código a
uint_fast64_t isum=0, isumsquared=0;
for(uint_fast32_t i = 0; i < thismaxval; i++) {
isum += i * frame->histo[i];
isumsquared += (i * i) * frame->histo[i];
}
funciona ~ 10 veces más rápido. Sin embargo, este impacto en el rendimiento también varía según la plataforma. En la estación de trabajo, una CPU Core i7 950 @ 3.07GHz el código es 10 veces más rápido. En mi Macbook8,1, que tiene un Intel Core i7 Sandy Bridge 2.7 GHz (2620M), el código es solo 2 veces más rápido.
Ahora me pregunto:
- ¿Por qué es el código original de manera lenta y fácilmente aceleró?
- ¿Por qué esto varía tanto por plataforma?
Actualización:
He compilado el código anterior con
g++ -O3 -Wall cast_test.cc -o cast_test
Update2:
que corrieron los códigos optimizados a través de un generador de perfiles (Instruments en Mac, como Shark) y encontró dos cosas:
1) El bucle en sí lleva una cantidad considerable de tiempo en algunos casos. thismaxval
es del tipo size_t
.
for(size_t i = 0; i < thismaxval; i++)
toma el 17% de mi tiempo total de ejecuciónfor(uint_fast32_t i = 0; i < thismaxval; i++)
dura 3,5%for(int i = 0; i < thismaxval; i++)
no aparece en el generador de perfiles, supongo que es menor que 0.1%
2) los tipos de datos y la materia de fundición como sigue:
sumsquared += (double)(i * i) * histo[i];
15% (consize_t i
)sumsquared += (double)(i * i) * histo[i];
36% (conuint_fast32_t i
)isumsquared += (i * i) * histo[i];
13% (conuint_fast32_t i
,uint_fast64_t isumsquared
)isumsquared += (i * i) * histo[i];
11% (conint i
,uint_fast64_t isumsquared
)
Sorprendentemente, int
es más rápido que uint_fast32_t
?
Update4:
me encontré con algunas pruebas más con diferentes tipos de datos y los distintos compiladores, en una máquina. Los resultados son los siguientes.
Para testd 0 - 2 en el código en cuestión es
for(loop_t i = 0; i < thismaxval; i++)
sumsquared += (double)(i * i) * histo[i];
con sumsquared
un doble, y loop_t
size_t
, uint_fast32_t
y int
para las pruebas de 0, 1 y 2.
Para las pruebas 3-- 5 el código es
for(loop_t i = 0; i < thismaxval; i++)
isumsquared += (i * i) * histo[i];
con isumsquared
de tipo uint_fast64_t
y loop_t
de nuevo size_t
, uint_fast32_t
y int
para las pruebas 3, 4 y 5.
Los compiladores I utilizado son 4.2.1 gcc, gcc 4.4.7, 4.6.3 gcc gcc y 4.7.0. Los tiempos son en porcentajes del tiempo de CPU total del código, por lo que muestran un rendimiento relativo, no absoluto (aunque el tiempo de ejecución fue bastante constante en 21 s). El tiempo de CPU es para ambas líneas, porque no estoy seguro de si el generador de perfiles separa correctamente las dos líneas de código.
gcc: 4.2.1 4.4.7 4.6.3 4.7.0 ---------------------------------- test 0: 21.85 25.15 22.05 21.85 test 1: 21.9 25.05 22 22 test 2: 26.35 25.1 21.95 19.2 test 3: 7.15 8.35 18.55 19.95 test 4: 11.1 8.45 7.35 7.1 test 5: 7.1 7.8 6.9 7.05
o:
Sobre la base de esto, parece que la fundición es costoso, no importa qué tipo entero que uso.
Además, parece que gcc 4.6 y 4.7 no pueden optimizar el bucle 3 (size_t y uint_fast64_t) correctamente.
¿podría probarlo con 'uint_fast32_t'? Una conjetura salvaje es que es más rápido debido al hecho de que el segundo tipo de datos tiene la misma longitud de bits que las instrucciones de la máquina (64 bits). Adivinando que tienes una máquina de 64 bits al menos. Yo esperaría que el fast32 también sea más lento. [edit] ¿podrías probar también el tamaño de 'uint_fast32_t' y' uint_fast64_t'? Mi suposición es que el 32 es en realidad 64 bits. – Yuri
¿Quieres decir 'uint_fast32_t isum'? Podría intentarlo, aunque creo que podría desbordarse, y es por eso que usé uint_fast64_t. – Tim
Bueno, para 1 .: La razón de alguna manera dicta que fundir ints a flotantes y hacer operaciones float debería ser más lento que hacer operaciones int directamente (aunque int-to-float no debería ser tan malo como float-to-int), incluso más así que con la pila x87 no óptima. ¿Compila con soporte SSE? –