Rendimiento de reparto de size_t a double
TL; DR: ¿Por qué está multiplicando / lanzando datos ensize_t
lento y por qué esto varía según la plataforma?
Estoy teniendo algunos problemas de rendimiento que no entiendo completamente. El contexto es un capturador de fotogramas de la cámara en el que se lee una imagen de uint16_t de 128x128 y se procesa posteriormente a una velocidad de varios 100 Hz.
En el post-procesamiento genero un histograma.frame->histo
que es deuint32_t
y tienethismaxval
= 2 ^ 16 elementos, básicamente cuento todos los valores de intensidad. Usando este histograma calculo 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];
}
Al perfilar el código con el perfil obtuve lo siguiente (muestras, porcentaje, 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 ocupa el 32% del tiempo de CPU, la segunda línea el 64%.
Hice algunas evaluaciones comparativas y parece ser el tipo de datos / casting 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];
}
corre ~ 10x más rápido. Sin embargo, este impacto de 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 estoy preguntando:
¿Por qué el código original es tan lento y fácil de acelerar?¿Por qué esto varía tanto por plataforma?Actualizar:
Compilé el código anterior con
g++ -O3 -Wall cast_test.cc -o cast_test
Actualización2:
Ejecuté los códigos optimizados a través de un perfilador (Instrumentos en Mac, comoTiburón) y encontré dos cosas:
1) El bucle en sí toma una cantidad de tiempo considerable en algunos casos.thismaxval
es de tiposize_t
.
for(size_t i = 0; i < thismaxval; i++)
toma el 17% de mi tiempo de ejecución totalfor(uint_fast32_t i = 0; i < thismaxval; i++)
lleva 3,5%for(int i = 0; i < thismaxval; i++)
no aparece en el perfilador, asumo que es menos del 0.1%2) Los tipos de datos y el reparto son los siguientes:
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
)Asombrosamente,int
es más rápido queuint_fast32_t
?
Actualización 4:
Corrí algunas pruebas más con diferentes tipos de datos y compiladores diferentes, en una máquina. Los resultados son los siguientes.
Para testd 0 - 2 el código relevante es
for(loop_t i = 0; i < thismaxval; i++)
sumsquared += (double)(i * i) * histo[i];
consumsquared
un doble, yloop_t
size_t
, uint_fast32_t
yint
para las pruebas 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];
conisumsquared
de tipouint_fast64_t
yloop_t
otra vezsize_t
, uint_fast32_t
yint
Para las pruebas 3, 4 y 5.
Los compiladores que utilicé son gcc 4.2.1, gcc 4.4.7, gcc 4.6.3 y gcc 4.7.0. Los tiempos están en porcentajes del tiempo total de cpu del código, por lo que muestran un rendimiento relativo, no absoluto (aunque el tiempo de ejecución fue bastante constante a los 21 s). El tiempo de la CPU es para las dos líneas, porque no estoy seguro de si el generador de perfiles separó 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:
Según esto, parece que la conversión es costosa, independientemente del tipo de entero que use.
Además, parece que gcc 4.6 y 4.7 no pueden optimizar el bucle 3 (size_t y uint_fast64_t) correctamente.