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

Pra*_*tic 26 c++ performance memory-management icc

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

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

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

接下来的一切只是基准程序的细节.

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

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

new memory allocation, cycles            12168
malloc allocation, cycles                 5144
Run Code Online (Sandbox Code Playgroud)

借助英特尔的icc:

new    memory allocation clock cycles     7251
malloc memory allocation clock cycles    52372
Run Code Online (Sandbox Code Playgroud)

我是如何使用malloc的:

volatile float* numbers = (float*)malloc(sizeof(float)*size);
Run Code Online (Sandbox Code Playgroud)

我是如何使用新的:

volatile float* numbers = new float[size];
Run Code Online (Sandbox Code Playgroud)

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");
Run Code Online (Sandbox Code Playgroud)

函数后面的宏:

#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;
Run Code Online (Sandbox Code Playgroud)

因此,对于new的夹心宏的分配调用是这样的:

CYCLE_COUNT_START
volatile float* numbers = new float[size];
CYCLE_COUNT_END
Run Code Online (Sandbox Code Playgroud)

之后,我检查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
Run Code Online (Sandbox Code Playgroud)

并使用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
Run Code Online (Sandbox Code Playgroud)

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

编辑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
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

观察到的差异不太可能偶然发生.

编译器和分配方法的密度估计:

内存分配期间经过的时钟周期的概率密度估计

差异现在不那么显着了,但icc的顺序仍然与预期相反.

编辑2

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

编辑3

源代码和脚本

编辑4

相同的数据重新绘制为前100个分配的时间段. 每个内存分配的时钟周期的时间进程

gna*_*729 1

它不是那样工作的。处理器和操作系统很复杂。您不能仅花费几微秒进行一次调用并期望获得有意义的计时信息。对于初学者来说,另一个应用程序可能会使用您的 CPU 一段时间,并且 RDTSC 将继续计数。