首页 文章

为什么malloc比英特尔的icc新7倍?

提问于
浏览
26

我为malloc与new进行了基准测试,以分配浮点数组 . 我的理解是malloc执行的操作是new执行的操作的一个子集 - malloc只是分配但是新的分配和构造,尽管我不确定这对于原语是否有意义 .

使用gcc对结果进行基准测试可以得出预期的行为 . malloc()更快 . 甚至有些问题与此问题相反 .

使用icc malloc可能比新的慢7倍 . 怎么可能?!

Everything that follows is just details of the benchmarking procedure.

对于基准测试,我使用了最近描述的协议by Intel . 这是我的结果 .

使用GNU的gcc分配4000个浮点数时,经过了时钟周期:

new memory allocation, cycles            12168
malloc allocation, cycles                 5144

借助英特尔的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];

volatile是存在的,因为在之前的基准测试尝试中,我遇到了一些问题,这些编译器优化了整个函数调用并生成只存储常量的程序 . (编译器选择以这种方式优化的函数实现确实比它没有的那些更快!)我尝试使用volatile去除以确保结果是相同的 .

我将要在两个宏之间进行基准测试的代码部分夹在中间 .

功能前的宏:

#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

这是在2012 MacBook Pro上提供的corei7-avx指令 . 我将'as'二进制文件与一个匹配here的脚本交换,以便gcc可以使用AVX指令 .

EDIT 1

要回答那些想要查看更多循环迭代的人,请浏览英特尔链接然后发布 . 另一方面,我可能会有相同的反应,所以这里是循环迭代 .

数组大小仍然是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的顺序仍然与预期相反 .

EDIT 2

对于char数组,结果几乎相同 . 因为sizeof(int)给我4和sizeof(char)给我1我将数组长度增加到16,000 .

EDIT 3

source code and scripts

EDIT 4

相同的数据重新绘制为前100个分配的时间段 .
timecourse of clock cycles per memory allocation

1 回答

  • 1

    它不像那样工作 . 处理器和操作系统很复杂 . 你不能只需要花费几微秒进行一次通话,并期望得到有意义的时间信息 . 对于初学者,另一个应用程序可以使用你的CPU一点,RDTSC将继续计数 .

相关问题