判断上百万个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只是我的准备工作,不算在运行时间内的)我在一台开发机上跑了一下,结果是:
cp命令的算法显然比我的快。大家可以自己在机器上试试,如果结果不一样,咱们再研究研究 :)
另外,要注意那句
if (memcmp(a, b, SIZE))
printf("wrong\n");
不能直接写成
memcmp(a,b,SIZE);
因为只写“memcp“的话my_process函数会被编译器优化掉(因为函数没有返回值,也没有改全局变量,也就是说,没有任何”输出“),我猜 bernie 遇到的就是这个问题
如果zero_buffer是静态或者全局,不会每次都分配的话,速度还有这么大差异么?
呵呵,你误解了,我本来就只有memcmp放在循环里,我不会每次循环都去malloc的。
速度确实有很大差异,当然,可能不同cache大小的CPU效果会有不同。
神奇,竟然会想到 先分配内存,在memset,再memcmp这么绕的方法,快才怪呢....
同学,你不看上面的评论我就再重复一遍:我要判断很多4k buffer是否全0,我的循环里没有malloc和memset,只有memcmp!!
不仅仅是效率的问题,而是编程思维习惯的问题。C语言的习惯是使用指针实现很多细节的操作,而很多面向对象语言都提供了高层的直接比较操作。从字符串操作的各种函数的实现就可以看出来,C语言使用指针来对字符串的每一个字符进行操作,而Java等则直接.equals或者==了。
看样子 memcmp 可能是按 byte 来比较的。cp 用的是 long ,如果循环的话,应该会比 memcmp 少,另外如果考虑到对齐的话用 单个 long 的速度也会比 byte 快,当你把代码汇编到 MIPS 的话,效果应该会更明显。记得在 Writing Clean Code 里作者用 memset 作例子,也是用 long 来填充,和 shell 的 cp 实现比较像。
我本地ubuntu测试这个比较代码
发现如果buffer 4k全为0的话 第一种做法是比第二种快的。
但是如果buffer 第一个字节就不为0的话 第二种是会比第一种快的。
为什么呢?
楼主,我的测试怎么是你的那种方法快呢,差不多是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