Para la diferencia de rendimiento del bucle y la optimización del compilador
Elegí la respuesta de David porque él era el único en presentar una solución a la diferencia en los bucles for sin bucles de optimización. Las otras respuestas demuestran lo que sucede cuando se activan los indicadores de optimización.
La respuesta de Jerry Coffin explicó lo que sucede al configurar las banderas de optimización para este ejemplo. Lo que queda sin respuesta es por qué superCalculationA se ejecuta más lentamente que superCalculationB, cuando B realiza una referencia de memoria adicional y una adición para cada iteración. La publicación de Nemo muestra la salida del ensamblador. Confirmé esta compilación con el-S
bandera en mi PC, Sandy Bridge de 2.9GHz (i5-2310), ejecutando Ubuntu 12.04 de 64 bits, como lo sugiere Matteo Italia.
Estaba experimentando con el rendimiento for-loops cuando me topé con el siguiente caso.
Tengo el siguiente código que hace el mismo cálculo de dos maneras diferentes.
#include <cstdint>
#include <chrono>
#include <cstdio>
using std::uint64_t;
uint64_t superCalculationA(int init, int end)
{
uint64_t total = 0;
for (int i = init; i < end; i++)
total += i;
return total;
}
uint64_t superCalculationB(int init, int todo)
{
uint64_t total = 0;
for (int i = init; i < init + todo; i++)
total += i;
return total;
}
int main()
{
const uint64_t answer = 500000110500000000;
std::chrono::time_point<std::chrono::high_resolution_clock> start, end;
double elapsed;
std::printf("=====================================================\n");
start = std::chrono::high_resolution_clock::now();
uint64_t ret1 = superCalculationA(111, 1000000111);
end = std::chrono::high_resolution_clock::now();
elapsed = (end - start).count() * ((double) std::chrono::high_resolution_clock::period::num / std::chrono::high_resolution_clock::period::den);
std::printf("Elapsed time: %.3f s | %.3f ms | %.3f us\n", elapsed, 1e+3*elapsed, 1e+6*elapsed);
start = std::chrono::high_resolution_clock::now();
uint64_t ret2 = superCalculationB(111, 1000000000);
end = std::chrono::high_resolution_clock::now();
elapsed = (end - start).count() * ((double) std::chrono::high_resolution_clock::period::num / std::chrono::high_resolution_clock::period::den);
std::printf("Elapsed time: %.3f s | %.3f ms | %.3f us\n", elapsed, 1e+3*elapsed, 1e+6*elapsed);
if (ret1 == answer)
{
std::printf("The first method, i.e. superCalculationA, succeeded.\n");
}
if (ret2 == answer)
{
std::printf("The second method, i.e. superCalculationB, succeeded.\n");
}
std::printf("=====================================================\n");
return 0;
}
Compilando este código con
g ++ main.cpp -o salida --std = c ++ 11
conduce al siguiente resultado:
=====================================================
Elapsed time: 2.859 s | 2859.441 ms | 2859440.968 us
Elapsed time: 2.204 s | 2204.059 ms | 2204059.262 us
The first method, i.e. superCalculationA, succeeded.
The second method, i.e. superCalculationB, succeeded.
=====================================================
Mi primera pregunta es:¿Por qué el segundo ciclo se ejecuta un 23% más rápido que el primero?
Por otro lado, si compilo el código con
g ++ main.cpp -o salida --std = c ++ 11 -O1
Los resultados mejoran mucho,
=====================================================
Elapsed time: 0.318 s | 317.773 ms | 317773.142 us
Elapsed time: 0.314 s | 314.429 ms | 314429.393 us
The first method, i.e. superCalculationA, succeeded.
The second method, i.e. superCalculationB, succeeded.
=====================================================
y la diferencia en el tiempo casi desaparece.
Pero no podía creer lo que veía cuando puse la bandera -O2,
g ++ main.cpp -o salida --std = c ++ 11 -O2
y obtuve esto:
=====================================================
Elapsed time: 0.000 s | 0.000 ms | 0.328 us
Elapsed time: 0.000 s | 0.000 ms | 0.208 us
The first method, i.e. superCalculationA, succeeded.
The second method, i.e. superCalculationB, succeeded.
=====================================================
Entonces, mi segunda pregunta es:¿Qué hace el compilador cuando configuro los indicadores -O1 y -O2 que conducen a esta mejora de rendimiento gigantesca?
lo comprobéOpción optimizada: uso de GNU Compiler Collection (GCC), pero eso no aclaraba las cosas.
Por cierto, estoy compilando este código con g ++ (GCC) 4.9.1.
EDITAR para confirmar la suposición de Basile Starynkevitch
Edité el código, ahoramain
Se ve como esto:
int main(int argc, char **argv)
{
int start = atoi(argv[1]);
int end = atoi(argv[2]);
int delta = end - start + 1;
std::chrono::time_point<std::chrono::high_resolution_clock> t_start, t_end;
double elapsed;
std::printf("=====================================================\n");
t_start = std::chrono::high_resolution_clock::now();
uint64_t ret1 = superCalculationB(start, delta);
t_end = std::chrono::high_resolution_clock::now();
elapsed = (t_end - t_start).count() * ((double) std::chrono::high_resolution_clock::period::num / std::chrono::high_resolution_clock::period::den);
std::printf("Elapsed time: %.3f s | %.3f ms | %.3f us\n", elapsed, 1e+3*elapsed, 1e+6*elapsed);
t_start = std::chrono::high_resolution_clock::now();
uint64_t ret2 = superCalculationA(start, end);
t_end = std::chrono::high_resolution_clock::now();
elapsed = (t_end - t_start).count() * ((double) std::chrono::high_resolution_clock::period::num / std::chrono::high_resolution_clock::period::den);
std::printf("Elapsed time: %.3f s | %.3f ms | %.3f us\n", elapsed, 1e+3*elapsed, 1e+6*elapsed);
std::printf("Results were %s\n", (ret1 == ret2) ? "the same!" : "different!");
std::printf("=====================================================\n");
return 0;
}
Estas modificaciones realmente aumentaron el tiempo de cálculo, tanto para-O1
y-O2
. Ambos me están dando alrededor de 620 ms ahora.Lo que demuestra que -O2 realmente estaba haciendo algunos cálculos en tiempo de compilación.
Todavía no entiendo qué están haciendo estas banderas para mejorar el rendimiento, y-Ofast
lo hace aún mejor, a unos 320 ms.
Observe también que he cambiado el orden en que se llaman las funciones A y B para probar la suposición de Jerry Coffin. Compilar este código sin indicadores de optimizador todavía me da alrededor de 2.2 segundos en B y 2.8 segundos en A. Así que me imagino que no es una cosa de caché. Solo reforzando que soyno hablando de optimización en el primer caso (el que no tiene banderas), solo quiero saber qué hace que el bucle de segundos se ejecute más rápido que el primero.