Подтвердить что ты не робот

Почему malloc 7x медленнее, чем новый для Intel icc?

Я сравнивал 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

Наблюдаемая разница вряд ли произошла случайно.

Оценки плотности для компиляторов и методы распределения:

probability density estimates of elapsed clock cycles during memory allocation

Разница теперь менее драматична, но порядок icc по-прежнему остается обратным ожидаемому.

РЕДАКТИРОВАТЬ 2

Результаты почти идентичны для массива char. Поскольку sizeof (int) дает мне 4, а sizeof (char) дает мне 1, я увеличил длину массива до 16 000.

РЕДАКТИРОВАТЬ 3

исходный код и скрипты

EDIT 4

Те же данные, которые были заменены на временные периоды для первых 100 распределений. timecourse of clock cycles per memory allocation

4b9b3361

Ответ 1

Это не работает. Процессоры и операционные системы сложны. Вы не можете просто сделать один звонок за несколько микросекунд и рассчитывать получить значимую информацию о времени. Для начала другое приложение может использовать ваш процессор немного, и RDTSC продолжит подсчет.