为什么malloc比我的测试程序中的静态内存分配更快?

时间:2015-12-29 07:01:23

标签: c memory-management

我有一个测试程序。我在ubuntu trusty 64位执行它时得到这个结果。

  

malloc时间:9571

     

静态时间:45587

为什么malloc比静态内存分配更快,或者我的测试程序错误?

测试程序是这样的。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>

#define TIME 10000

int data[1024] = { 1,2,3,4,5,6,6,7,8,5,4,3,2,3 };
int st[TIME][1024];
int main(void) {
    int i = 0;
    int time = 0;
    struct timeval tv1,tv2;

    /* test for malloc */
    memset(&tv1,0,sizeof(tv1));
    memset(&tv2,0,sizeof(tv2));
    gettimeofday(&tv1,NULL);
    for(i=0;i<TIME;i++) {
        void * p = malloc(4096);
        memset(p,0,4096);
        memcpy(p,data,sizeof(data));
        free(p);
        p = NULL;
    }
    gettimeofday(&tv2,NULL);
    time = ((tv2.tv_sec - tv1.tv_sec) * 1000000 +
        (tv2.tv_usec - tv1.tv_usec));
    printf("malloc time:%d\n",time);

    /* test for static memory allocation */
    memset(&tv1,0,sizeof(tv1));
    memset(&tv2,0,sizeof(tv2));
    gettimeofday(&tv1,NULL);
    for(i=0;i<TIME;i++) {
        memset(st[i],0,4096);
        memcpy(st[i],data,sizeof(data));
    }
    gettimeofday(&tv2,NULL);
    time = ((tv2.tv_sec - tv1.tv_sec) * 1000000 +
        (tv2.tv_usec - tv1.tv_usec));
    printf("static time:%d\n",time);

    return 0;
}

2 个答案:

答案 0 :(得分:3)

该基准测试基本上没有意义,因为它测量的大部分内容与使用两个存储区域的关系不大。

当您的程序启动时(即,main的执行开始时),默认初始化的数据段(即40兆字节的st[40000][1024])尚未映射到物理记忆。已将虚拟内存地址标记为延迟映射到零初始化内存,但直到程序实际尝试引用这些地址时才会发生这种情况。每个这样的引用都需要内核干预来调整虚拟内存映射(以及零初始化物理内存页面)。在该干预之后,映射虚拟内存页面,但不映射同一数据段中的任何其他页面。因此,当您遍历st数组时,将生成大量页面错误,每个错误都需要大量时间。您在“静态内存”测试中测量的绝大多数时间都是由这些内核陷阱组成的。

另一方面,第一次调用malloc将导致标准库初始化内存分配系统。虽然初始化不是太复杂,但它也不是免费的。因此,在“malloc'ed memory”测试中测量的大部分时间都包含初始化。

为了进行有意义的基准测试,您需要确保在开始测量之前已完成所有延迟初始化。一种方法是在同一个可执行文件中多次执行基准测试,并丢弃第一次(或前几次)重复。

作为一个例子,我通过在main的整个内容周围添加以下循环(返回语句除外)来简单地修改基准:

for (int reps = 0; reps < 4; ++reps) {
  printf("Repetition %d\n", reps);
  /* Body of main goes here */
}

这导致以下输出:

Repetition 0:
malloc time:9584
static time:26923
Repetition 1:
malloc time:2467
static time:4360
Repetition 2:
malloc time:2463
static time:4332
Repetition 3:
malloc time:2413
static time:4609

注意“预热”迭代(重复0)和剩余的迭代次数之间的差异。

这仍然会在动态和静态内存测试之间留下差异。在这里,值得注意的是,这两个测试以不同的方式使用内存。 malloc测试(可能)在每次迭代时重用相同的缓冲区,因为在free一块内存之后,该大小的下一个malloc可能会立即返回它。另一方面,静态内存测试循环遍历整个40MB的分配。更好的比较是mallocst大小相同的缓冲区,并使用与静态测试相同的代码循环访问它,或者使st成为1024个整数的单个向量(与malloc一样)并使用与malloc测试相同的代码重用它。换句话说,为了比较两种可能的方法,最小化两者之间的差异。 (我会把它留作练习。)

如果您进行了这些建议的更改,您可能会发现差异会降低到噪音。但是有可能会保持一些一致(但很小)的差异,这将反映出两种循环之间差异的难以控制的多样性,包括代码和数据的对齐,以及CPU缓存的精确细节。这些微小的差异将符合“巧合”的范畴,正如@seb's answer to this question中明确表达的那样。 (虽然我认为理解基准测试中可避免的陷阱很重要,但我要强调@ seb在该问题上的建议无疑是正确的。)

答案 1 :(得分:2)

巧合不是因果关系。您的CPU代码缓存可能很适合第一个循环(测试malloc),然后第二个循环需要从较慢的主内存中提取到更快的代码缓存中,这会偏向您的第二个时间。

对于数据可能存在类似的缓存,这意味着静态对象st将缓存在比malloc(或反之亦然)为其全部分配的内存更快的内存中缓存持续时间。重点应该放在可能上。没有要求这种情况。纯粹巧合的是,正如你已经注意到的那样,一个比另一个快。

您不应该通过滚动自己的分析器来测试理论瓶颈的速度,例如确定更快的存储持续时间来执行瓶颈分析,因为这只能导致在整个解决方案中有利于过早优化的结论,而不是大多数干净的代码,可能只是一点点混乱。

相反,您应该专注于使用干净,可维护的代码解决实际问题。一旦你有一个解决实际问题的程序,你应该使用你的探查器来确定它是否足够快,如果没有,你的代码部分要优化,所以你可以希望保持大部分代码清洁