c++ performance optimization

c++ - ¿Por qué es std:: acumular tan lento?



performance optimization (2)

Esta pregunta ya tiene una respuesta aquí:

Estoy tratando de sumar elementos de matriz utilizando un bucle for simple, un std::accumulate y un bucle enrollado manualmente para. Como es de esperar, el bucle desenrollado manualmente es el más rápido, pero más interesante es que std :: acumulación es mucho más lento que el bucle simple. Este es mi código, lo compilé con gcc 4.7 con el indicador -O3. Visual Studio necesitará una implementación diferente de la función rdtsc.

#include <iostream> #include <algorithm> #include <numeric> #include <stdint.h> using namespace std; __inline__ uint64_t rdtsc() { uint64_t a, d; __asm__ volatile ("rdtsc" : "=a" (a), "=d" (d)); return (d<<32) | a; } class mytimer { public: mytimer() { _start_time = rdtsc(); } void restart() { _start_time = rdtsc(); } uint64_t elapsed() const { return rdtsc() - _start_time; } private: uint64_t _start_time; }; // timer int main() { const int num_samples = 1000; float* samples = new float[num_samples]; mytimer timer; for (int i = 0; i < num_samples; i++) { samples[i] = 1.f; } double result = timer.elapsed(); std::cout << "rewrite of " << (num_samples*sizeof(float)/(1024*1024)) << " Mb takes " << result << std::endl; timer.restart(); float sum = 0; for (int i = 0; i < num_samples; i++) { sum += samples[i]; } result = timer.elapsed(); std::cout << "naive:/t/t" << result << ", sum = " << sum << std::endl; timer.restart(); float* end = samples + num_samples; sum = 0; for(float* i = samples; i < end; i++) { sum += *i; } result = timer.elapsed(); std::cout << "pointers:/t/t" << result << ", sum = " << sum << std::endl; timer.restart(); sum = 0; sum = std::accumulate(samples, end, 0); result = timer.elapsed(); std::cout << "algorithm:/t" << result << ", sum = " << sum << std::endl; // With ILP timer.restart(); float sum0 = 0, sum1 = 0; sum = 0; for (int i = 0; i < num_samples; i+=2) { sum0 += samples[i]; sum1 += samples[i+1]; } sum = sum0 + sum1; result = timer.elapsed(); std::cout << "ILP:/t/t" << result << ", sum = " << sum << std::endl; }


Como a usted (aparentemente) le importa hacer esto rápido, también podría considerar tratar de multiprocilar el cálculo para aprovechar todos los núcleos disponibles. Hice una reescritura bastante trivial de tu bucle ingenuo para usar OpenMP, dando esto:

timer.restart(); sum = 0; // only real change is adding the following line: #pragma omp parallel for schedule(dynamic, 4096), reduction(+:sum) for (int i = 0; i < num_samples; i++) { sum += samples[i]; } result = timer.elapsed(); std::cout << "OMP:/t/t" << result << ", sum = " << sum << std::endl;

Solo para sonreír, también reescribí un poco en su bucle desenrollado para permitir el desenrollamiento semi-arbitrario, y también agregué OpenMP:

static const int unroll = 32; real total = real(); timer.restart(); double sum[unroll] = { 0.0f }; #pragma omp parallel for reduction(+:total) schedule(dynamic, 4096) for (int i = 0; i < num_samples; i += unroll) { for (int j = 0; j < unroll; j++) total += samples[i + j]; } result = timer.elapsed(); std::cout << "ILP+OMP:/t" << result << ", sum = " << total << std::endl;

También aumenté el tamaño de la matriz (sustancialmente) para obtener números un tanto más significativos. Los resultados fueron los siguientes. Primero para un AMD de doble núcleo:

rewrite of 4096 Mb takes 8269023193 naive: 3336194526, sum = 536870912 pointers: 3348790101, sum = 536870912 algorithm: 3293786903, sum = 536870912 ILP: 2713824079, sum = 536870912 OMP: 1885895124, sum = 536870912 ILP+OMP: 1618134382, sum = 536870912

Entonces para un quad-core (Intel i7):

rewrite of 4096 Mb takes 2415836465 naive: 1382962075, sum = 536870912 pointers: 1675826109, sum = 536870912 algorithm: 1748990122, sum = 536870912 ILP: 751649497, sum = 536870912 OMP: 575595251, sum = 536870912 ILP+OMP: 450832023, sum = 536870912

Desde el punto de vista de las cosas, las versiones de OpenMP probablemente tienen limitaciones en el ancho de banda de la memoria; las versiones de OpenMP hacen más uso de la CPU que las versiones sin subprocesos, pero aún así alcanzan alrededor del 70% aproximadamente, lo que indica otras La CPU está actuando como un cuello de botella.


Para empezar, su uso de std::accumulate es sumar enteros. Por lo tanto, es probable que esté pagando el costo de convertir cada punto flotante en entero antes de agregarlo. Tratar:

sum = std::accumulate( samples, end, 0.f );

y ver si eso no hace una diferencia.