Я сравнивал malloc и new для выделения массива поплавков. Мое понимание заключалось в том, что операции, выполняемые malloc, являются подмножеством операций, выполняемых новым - что malloc просто выделяет, но новые выделяют и конструируют, хотя я не уверен, что это значимая разница для примитивов.
Результаты бенчмаркинга с gcc дают ожидаемое поведение. malloc() работает быстрее. Есть даже вопросы SO, задающие противоположное этому.
С icc malloc может быть на 7x медленнее, чем новый. Как возможно?!
Все, что следует за этим, - это просто детали процедуры бенчмаркинга.
Для бенчмаркинга я использовал недавно описанный протокол от Intel. Вот мои результаты.
Циклы часов, прошедшие при распределении массива из 4000 поплавков с GNU gcc:
new memory allocation, cycles 12168
malloc allocation, cycles 5144
И с Intel icc:
new memory allocation clock cycles 7251
malloc memory allocation clock cycles 52372
Как я использую malloc:
volatile float* numbers = (float*)malloc(sizeof(float)*size);
Как я использую новое:
volatile float* numbers = new float[size];
Неустойчивость существует, потому что в предыдущих попытках бенчмаркинга у меня были проблемы с хитрыми компиляторами, оптимизирующими целые вызовы функций и генерирующие программы, которые просто хранят константы. (Реализации функций, которые компилятор решил оптимизировать таким образом, были действительно быстрее, чем те, которые у него не были!) Я попытался с удаленным удалением, чтобы быть уверенным, и результаты были одинаковыми.
Я сэндвич часть кода, который я хочу сравнить между двумя макросами.
Макрос, который приходит перед функцией:
#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");
Макрос, который приходит после функции:
#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;
Таким образом, вызов выделения с помощью макросов для добавления new:
CYCLE_COUNT_START
volatile float* numbers = new float[size];
CYCLE_COUNT_END
После этого я проверяю значение ellapsed_cycles, чтобы посмотреть, как все прошло.
И чтобы быть уверенным, что я не делаю что-то глупое, вот как я компилирую с 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
И с 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
Это на MacBook Pro 2012 года с инструкциями Corei7-avx. У меня есть двоичный файл 'as' с script, который соответствует здесь, так что gcc может использовать инструкции AVX.
РЕДАКТИРОВАТЬ 1
Чтобы ответить тем, кто хочет видеть больше итераций цикла, пропустите ссылку Intel, а затем опубликуйте. С другой стороны, у меня, вероятно, была бы такая же реакция, и вот здесь итерации цикла.
Размер массива по-прежнему равен 4000, и каждый запуск программы по-прежнему выполняет одно распределение памяти. Я не хотел менять то, что сравнивалось, выделяя более крупный массив, который не вписывается в L1 или многократно выделяет и освобождает память и поднимает другие вопросы о памяти. Программа запускается в цикле bash. Я запускаю 4 отдельные программы для теста, все 4 в каждой итерации цикла, чтобы уменьшить гетерогенность из-за других запущенных процессов.
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
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
Наблюдаемая разница вряд ли произошла случайно.
Оценки плотности для компиляторов и методы распределения:
Разница теперь менее драматична, но порядок icc по-прежнему остается обратным ожидаемому.
РЕДАКТИРОВАТЬ 2
Результаты почти идентичны для массива char. Поскольку sizeof (int) дает мне 4, а sizeof (char) дает мне 1, я увеличил длину массива до 16 000.
РЕДАКТИРОВАТЬ 3
EDIT 4
Те же данные, которые были заменены на временные периоды для первых 100 распределений.