代码小比较

判断上百万个4k的buffer是否为全0,我最先想到的办法是:
zero_buffer = malloc(4096);
memset(zero_buffer, 0, 4096);

/* 循环百万次读取buffer */
    if (!memcmp(zero_buffer, buffer)) {
        /* 全0 */
    }

做完以后跑了一下,速度凑合。由于好奇,看看shell工具cp的代码,它的解决办法是:

/* 循环百万次读取buffer */
    long *wp = buffer;
    while (*wp++ ==0 && wp<buffer + 4096)
        continue;

    if (wp >= buffer + 4096) {
        /* 全0 */
    }

cp的代码真称得上千锤百炼,这比我的方法要快,因为我的办法需要CPU访问两块内存(buffer和zero_buffer),而cp只问一块内存,CPU的cache会高效得多。
判断一块4k buffer当然看不出快慢,我们循环上百万次,立刻就能看出差别:cp的算法比我的快了3倍。


=== 2012.04.19 ===

首先感谢 bernie 的提问,我自己写了完整的代码来验证这个事儿:

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#define SIZE (1024*1024*1024)

void my_process(void *a, void *b)
{
if (memcmp(a, b, SIZE))
printf("wrong\n");
}

void cp_process(void *a)
{
long *wp = a;
while (*wp++ == 0 && wp < (long *)(a + SIZE))
continue;
}

int main(int argc ,char *argv[])
{
struct timeval start, end;

char * zero_buffer = (char*)malloc(SIZE);
memset(zero_buffer, 0, SIZE);
char * buffer = (char*)malloc(SIZE);
memset(buffer, 0, SIZE);

gettimeofday(&start, NULL);
my_process(buffer, zero_buffer);
gettimeofday(&end, NULL);
printf("my:%lu\n", (end.tv_sec * 1000000 + end.tv_usec) -
(start.tv_sec * 1000000 + start.tv_usec));

gettimeofday(&start, NULL);
cp_process(buffer);
gettimeofday(&end, NULL);
printf("my:%lu\n", (end.tv_sec * 1000000 + end.tv_usec) -
(start.tv_sec * 1000000 + start.tv_usec));
return 0;
}

绝对能编译,(大家注意,我只算内存比较的时间,什么malloc什么memset只是我的准备工作,不算在运行时间内的)我在一台开发机上跑了一下,结果是:

my:3428159
cp:611168

cp命令的算法显然比我的快。大家可以自己在机器上试试,如果结果不一样,咱们再研究研究 :)

另外,要注意那句
if (memcmp(a, b, SIZE))
printf("wrong\n");
不能直接写成
        memcmp(a,b,SIZE);
因为只写“memcp“的话my_process函数会被编译器优化掉(因为函数没有返回值,也没有改全局变量,也就是说,没有任何”输出“),我猜 bernie 遇到的就是这个问题


相关文章

分类

8 Comments

tinyfool said:

如果zero_buffer是静态或者全局,不会每次都分配的话,速度还有这么大差异么?

DongHao Author Profile Page said:

呵呵,你误解了,我本来就只有memcmp放在循环里,我不会每次循环都去malloc的。
速度确实有很大差异,当然,可能不同cache大小的CPU效果会有不同。

cc said:

神奇,竟然会想到 先分配内存,在memset,再memcmp这么绕的方法,快才怪呢....

DongHao Author Profile Page said:

同学,你不看上面的评论我就再重复一遍:我要判断很多4k buffer是否全0,我的循环里没有malloc和memset,只有memcmp!!

zhuwenger said:

不仅仅是效率的问题,而是编程思维习惯的问题。C语言的习惯是使用指针实现很多细节的操作,而很多面向对象语言都提供了高层的直接比较操作。从字符串操作的各种函数的实现就可以看出来,C语言使用指针来对字符串的每一个字符进行操作,而Java等则直接.equals或者==了。

w1e3 said:

看样子 memcmp 可能是按 byte 来比较的。cp 用的是 long ,如果循环的话,应该会比 memcmp 少,另外如果考虑到对齐的话用 单个 long 的速度也会比 byte 快,当你把代码汇编到 MIPS 的话,效果应该会更明显。记得在 Writing Clean Code 里作者用 memset 作例子,也是用 long 来填充,和 shell 的 cp 实现比较像。

lazycat said:

我本地ubuntu测试这个比较代码
发现如果buffer 4k全为0的话 第一种做法是比第二种快的。
但是如果buffer 第一个字节就不为0的话 第二种是会比第一种快的。
为什么呢?

bernie said:

楼主,我的测试怎么是你的那种方法快呢,差不多是2倍
void my_process()
{
char * zero_buffer = (char*)malloc(4096);
memset(zero_buffer, 0, 4096);

char * buffer = (char*)malloc(4096);
memset(buffer, 0, 4096);

int i = 0;
for(i = 0; i = buffer + 4096)
{

}
}
}

int main()
{
time_t start = time(0);

my_process();

time_t end = time(0);

printf("my:%lu\n", end-start);

start = time(0);
cp_process();
end = time(0);

printf("cp:%lu\n", end-start);

return 0;
}

答案:
my:2
cp:5

留言:

关于文章

This page contains a single entry by DongHao published on 02 15, 2011 2:14 PM.

家庭笑话三则 was the previous entry in this blog.

kernel的rlimit变化 is the next entry in this blog.

Find recent content on the main index or look in the 存档 to find all content.