c++ - ¿Por qué Malloc 7x es más lento que el nuevo icc de Intel?
performance memory-management (1)
No funciona así. Los procesadores y los sistemas operativos son complicados. No se puede simplemente hacer una sola llamada con unos pocos microsegundos y esperar obtener información de tiempo significativa. Para empezar, otra aplicación podría usar su CPU por un tiempo y RDTSC continuará contando.
Analicé Malloc vs. New para asignar una serie de flotadores. Según tengo entendido, las operaciones realizadas por malloc son un subconjunto de las operaciones realizadas por new, que malloc solo asigna pero asigna y construye nuevas, aunque no estoy seguro de si eso es una diferencia significativa para las primitivas.
Los resultados de benchmarking con gcc dan el comportamiento esperado. malloc () es más rápido. Incluso hay preguntas AS que preguntan lo contrario de este.
Con icc malloc puede ser 7 veces más lento que nuevo. ¡¿Como es posible?!
Todo lo que sigue es solo detalles del procedimiento de evaluación comparativa.
Para la evaluación comparativa, utilicé un protocolo recientemente descrito por Intel . Aquí están mis resultados.
Los ciclos de reloj transcurrieron al asignar una matriz de 4000 flotantes con gcc de GNU:
new memory allocation, cycles 12168
malloc allocation, cycles 5144
Y con icc de Intel:
new memory allocation clock cycles 7251
malloc memory allocation clock cycles 52372
Cómo estoy usando malloc:
volatile float* numbers = (float*)malloc(sizeof(float)*size);
Cómo uso nuevo:
volatile float* numbers = new float[size];
La volatilidad está ahí porque en intentos anteriores de evaluación comparativa he tenido problemas con compiladores astutos que optimizan llamadas de funciones completas y generan programas que solo almacenan constantes. (¡Las implementaciones de funciones que el compilador optó por optimizar de esta manera fueron de hecho más rápidas que las que no lo hicieron!) Intenté eliminar el volátil solo para asegurarme y los resultados fueron los mismos.
Emloco la parte del código que quiero comparar entre dos macros.
Macro que viene antes de una función:
#define CYCLE_COUNT_START /
asm volatile ("CPUID/n/t" /
"RDTSC/n/t" /
"mov %%edx, %0/n/t" /
"mov %%eax, %1/n/t": "=r" (cycles_high), "=r" (cycles_low):: /
"%rax", "%rbx", "%rcx", "%rdx");
Macro que viene después de una función:
#define CYCLE_COUNT_END /
asm volatile("RDTSCP/n/t" /
"mov %%edx, %0/n/t" /
"mov %%eax, %1/n/t" /
"CPUID/n/t": "=r" (cycles_high1), "=r" (cycles_low1):: /
"%rax", "%rbx", "%rcx", "%rdx"); /
start = ( ((uint64_t)cycles_high << 32) | cycles_low ); /
end = ( ((uint64_t)cycles_high1 << 32) | cycles_low1 ); /
ellapsed_cycles = end - start;
Entonces la llamada de asignación con las macros intercaladas para nuevas es esta:
CYCLE_COUNT_START
volatile float* numbers = new float[size];
CYCLE_COUNT_END
Después de eso, compruebo el valor de los cesped_cycles para ver cómo fue todo.
Y para asegurarme de que no estoy haciendo algo tonto, así es como estoy compilando con icc:
icc -O3 -ipo -no-prec-div -std=c++11 heap_version3.cpp -o heap_version3
icc -O3 -ipo -no-prec-div -std=c++11 malloc_heap_version3.cpp -o malloc_heap_version3
Y con gcc:
g++-4.8 -Ofast -march=native -std=c++11 heap_version3.cpp -o heap_version3
g++-4.8 -Ofast -march=native -std=c++11 malloc_heap_version3.cpp -o malloc_heap_version3
Esto está en una MacBook Pro 2012 con las instrucciones corei7-avx disponibles. Tengo el binario "como" intercambiado con un script que coincide con el que está here para que gcc pueda usar las instrucciones AVX.
EDIT 1
Para responder a aquellos que quieren ver más iteraciones de bucle, por favor revise el enlace de Intel y luego publíquelo. Por otro lado, probablemente tenga la misma reacción y estas son las iteraciones de bucle.
El tamaño de la matriz sigue siendo 4000 y cada ejecución del programa todavía tiene una sola asignación de memoria. No quería cambiar lo que se estaba evaluando asignando una matriz más grande que no encaja en L1 o asignando y desasignando repetidamente la memoria y planteando otras preguntas sobre la memoria. El programa se ejecuta en un bucle por bash. Ejecuto 4 programas separados para el punto de referencia, los 4 en cada iteración de bucle en un esfuerzo por reducir la heterogeneidad debido a otros procesos en ejecución.
for i in $(seq 1 10000); do
echo gcc malloc $(./gcc_malloc_heap_version3 | head -n 1 | cut -d" " -f 4-)
echo icc malloc $(./icc_malloc_heap_version3 | head -n 1 | cut -d" " -f 4-)
echo gcc new $(./gcc_heap_version3 | head -n 1 | cut -d" " -f 4-)
echo icc new $(./icc_heap_version3 | head -n 1 | cut -d" " -f 4-)
done
tiempos de asignación de memoria icc:
malloc new
Min. : 3093 1150
1st Qu.: 3729 1367
Median : 3891 1496
Mean : 4015 1571
3rd Qu.: 4099 1636
Max. :33231 183377
Welch Two Sample t-test
p-value < 2.2e-16
La diferencia observada es poco probable que haya ocurrido por casualidad.
Estimaciones de densidad para compiladores y métodos de asignación:
La diferencia ahora es menos dramática, pero el orden para icc sigue siendo el inverso de lo esperado.
EDIT 2
Los resultados son casi idénticos para una matriz de caracteres. Como sizeof (int) me da 4 y sizeof (char) me da 1, aumenté la longitud de la matriz a 16,000.
EDIT 3
EDIT 4
Los mismos datos reescritos como un tiempo para las primeras 100 asignaciones.