我有一个测试程序。我在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;
}
答案 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的分配。更好的比较是malloc
与st
大小相同的缓冲区,并使用与静态测试相同的代码循环访问它,或者使st
成为1024个整数的单个向量(与malloc一样)并使用与malloc测试相同的代码重用它。换句话说,为了比较两种可能的方法,最小化两者之间的差异。 (我会把它留作练习。)
如果您进行了这些建议的更改,您可能会发现差异会降低到噪音。但是有可能会保持一些一致(但很小)的差异,这将反映出两种循环之间差异的难以控制的多样性,包括代码和数据的对齐,以及CPU缓存的精确细节。这些微小的差异将符合“巧合”的范畴,正如@seb's answer to this question中明确表达的那样。 (虽然我认为理解基准测试中可避免的陷阱很重要,但我要强调@ seb在该问题上的建议无疑是正确的。)
答案 1 :(得分:2)
巧合不是因果关系。您的CPU代码缓存可能很适合第一个循环(测试malloc
),然后第二个循环需要从较慢的主内存中提取到更快的代码缓存中,这会偏向您的第二个时间。
对于数据可能存在类似的缓存,这意味着静态对象st
将缓存在比malloc
(或反之亦然)为其全部分配的内存更快的内存中缓存持续时间。重点应该放在可能上。没有要求这种情况。纯粹巧合的是,正如你已经注意到的那样,一个比另一个快。
您不应该通过滚动自己的分析器来测试理论瓶颈的速度,例如确定更快的存储持续时间来执行瓶颈分析,因为这只能导致在整个解决方案中有利于过早优化的结论,而不是大多数干净的代码,可能只是一点点混乱。
相反,您应该专注于使用干净,可维护的代码解决实际问题。一旦你有一个解决实际问题的程序,你应该使用你的探查器来确定它是否足够快,如果没有,你的代码部分要优化,所以你可以希望保持大部分代码清洁