我了解到 memset(ptr, 0, nbytes)
非常快,但是有没有更快的方法(至少在x86上)?
我假设 memset 使用 mov
,但是当清零内存时,大多数编译器使用 xor
,因为它更快,对吗?编辑1:错误的,正如GregS指出的那样,这只适用于寄存器。我在想什么呢?
另外,我请一位比我更熟悉汇编语言的人来查看stdlib,他告诉我,在x86上,memset没有充分利用32位宽的寄存器。然而,当时我非常累,所以我不确定我是否正确理解了他的话。
编辑2:我重新审视了这个问题,并进行了一些测试。以下是我测试的内容:
#include <stdio.h>
#include <malloc.h>
#include <string.h>
#include <sys/time.h>
#define TIME(body) do { \
struct timeval t1, t2; double elapsed; \
gettimeofday(&t1, NULL); \
body \
gettimeofday(&t2, NULL); \
elapsed = (t2.tv_sec - t1.tv_sec) * 1000.0 + (t2.tv_usec - t1.tv_usec) / 1000.0; \
printf("%s\n --- %f ---\n", #body, elapsed); } while(0) \
#define SIZE 0x1000000
void zero_1(void* buff, size_t size)
{
size_t i;
char* foo = buff;
for (i = 0; i < size; i++)
foo[i] = 0;
}
/* I foolishly assume size_t has register width */
void zero_sizet(void* buff, size_t size)
{
size_t i;
char* bar;
size_t* foo = buff;
for (i = 0; i < size / sizeof(size_t); i++)
foo[i] = 0;
// fixes bug pointed out by tristopia
bar = (char*)buff + size - size % sizeof(size_t);
for (i = 0; i < size % sizeof(size_t); i++)
bar[i] = 0;
}
int main()
{
char* buffer = malloc(SIZE);
TIME(
memset(buffer, 0, SIZE);
);
TIME(
zero_1(buffer, SIZE);
);
TIME(
zero_sizet(buffer, SIZE);
);
return 0;
}
结果:
除了 -O3 之外, zero_1 是最慢的。zero_sizet 是最快的,-O1,-O2 和 -O3 性能大致相等。memset 总是比 zero_sizet 慢。(在 -O3 下慢两倍)。有一件有趣的事情是,在 -O3 下,zero_1 和 zero_sizet 的速度相同。但是,反汇编函数有大约四倍的指令量(我认为这是由于循环展开引起的)。另外,我尝试进一步优化 zero_sizet,但编译器总是做得比我好,但毫无意外。
目前 memset 获胜,以前的结果受 CPU 缓存的影响而失真。(所有测试都在 Linux 上运行)需要进一步测试。我下一步将尝试使用汇编语言:)
编辑3:修复了测试代码中的错误,测试结果不受影响
编辑4:在浏览 VS2010 C 运行时反汇编时,我注意到 memset
具有 SSE 优化的零值例程。这将很难被超越。
memset
使用mov
呢?不同的编译器会采用不同的方式。如果在某种架构上xor
更快,那么一些编译器将优化memset(ptr, 0, nbytes)
为xor
指令也就不足为奇了。 - Laurence Gonsalvescalloc
可能是有效地免费的,因为实现可能会在 CPU 空闲时提前零化页面。这算吗?;-) - Steve Jessopzero_sizet
作弊了,因为它没有执行memset
的所有工作。只有在复制大小是sizeof(size_t)
的倍数时,它们才相等。在其他情况下,memset
也必须将剩余的1到3(7)字节清零。如果地址未对齐,则可能会出现另一个问题,良好的memset
实现将尝试在循环之前对其进行size_t*
对齐。这在正常情况下会稍微有点惩罚,但在奇怪的情况下会获得很大收益。一种良好的实现还会选择64位对齐作为PC上的内存总线自奔蒂厄姆1开始就是64位。 - Patrick Schlütermadvise()
可能什么也不做。它可能在 特定的内核和 libc 版本 上运行良好,但如果您升级其中任何一个,它很容易出现问题。 - tc.