我知道这memset(ptr, 0, nbytes)很快,但有更快的方法(至少在x86上)?
我假设memset使用mov,但是当大多数编译器将内存归零时,xor因为它更快,更正确吗?edit1:错了,因为GregS指出只适用于寄存器.我在想什么?
另外我问一个比我更熟悉汇编程序的人看看stdlib,他告诉我x86 memset没有充分利用32位宽的寄存器.然而当时我很累,所以我不太确定我是否理解正确.
edit2:我重新审视了这个问题并进行了一些测试.这是我测试的:
#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;
}
Run Code Online (Sandbox Code Playgroud)
结果:
除了-O3之外,zero_1是最慢的.zero_sizet是最快的,在-O1,-O2和-O3之间具有大致相同的性能.memset总是比zero_sizet慢.(-O3慢两倍).有趣的是,-O3 zero_1与zero_sizet同样快.然而,反汇编函数的指令大约是指令的四倍(我认为是由循环展开引起的).此外,我尝试进一步优化zero_sizet,但编译器总是超过我,但这并不奇怪.
对于现在的memset胜利,以前的结果被CPU缓存扭曲了.(所有测试都在Linux上运行)需要进一步测试.我接下来会尝试汇编程序:)
edit3:修复了测试代码中的bug,测试结果不受影响
edit4:在重新组装VS2010 C运行时时,我发现memset有一个SSE优化的例程为零.很难打败这个.
小智 32
x86是相当广泛的设备.
对于完全通用的x86目标,具有"rep movsd"的汇编块可能会在时间上将零值爆发到32位内存.尽量确保这项工作的大部分是DWORD对齐的.
对于带有mmx的芯片,带有movq的装配环一次可以达到64位.
您可能能够使用C/C++编译器来使用带有指向long long或_m64的指针的64位写入.目标必须是8字节对齐,以获得最佳性能.
对于带有sse的芯片,movaps很快,但只有当地址是16字节对齐时,所以使用movsb直到对齐,然后使用movaps循环完成清除
Win32有"ZeroMemory()",但我忘记了这是一个宏来memset,还是一个实际的"好"实现.
Ben*_*tto 26
memset通常设计为非常快速的通用设置/归零代码.它处理所有具有不同尺寸和对齐的情况,这会影响您可以用来执行工作的各种指令.根据您所使用的系统(以及您的stdlib来自哪个供应商),底层实现可能是特定于该体系结构的汇编程序,以利用其本机属性.它可能还有内部特殊情况来处理归零的情况(而不是设置其他值).
也就是说,如果你有非常特定的,非常关键的内存归零,那么你自己可以memset通过自己的方式来击败特定的实现.memset并且它在标准库中的朋友总是有趣的目标,一次胜利的编程.:)
Jen*_*edt 23
现在您的编译器应该为您完成所有工作.至少我知道gcc在优化调用时非常有效memset(尽管更好地检查汇编程序).
然后,memset如果您不需要,请避免:
... = { 0
}对堆栈内存使用正确的初始化()对于非常大的块,mmap如果你有它.这只是从系统"免费"获得零初始化内存.
如果我没记错的话(从几年前开始),其中一位高级开发人员正在谈论在PowerPC上快速使用bzero()(规格说我们需要在启动时将几乎所有内存归零).它可能无法很好地转换(如果有的话)到x86,但它可能值得探索.
想法是加载数据缓存行,清除该数据缓存行,然后将清除的数据缓存行写回内存.
对于它的价值,我希望它有所帮助.
memset 函数被设计得灵活简单,甚至以牺牲速度为代价。在许多实现中,它是一个简单的 while 循环,在给定的字节数上一次复制一个字节的指定值。如果您想要一个更快的 memset(或 memcpy、memmove 等),几乎总是可以自己编写一个。
最简单的定制是执行单字节“设置”操作,直到目标地址对齐为 32 位或 64 位(无论与您的芯片架构匹配),然后开始一次复制完整的 CPU 寄存器。如果您的范围没有以对齐地址结束,您可能必须在末尾执行几个单字节“设置”操作。
根据您的特定 CPU,您可能还有一些流 SIMD 指令可以帮助您。这些通常在对齐地址上工作得更好,因此上述使用对齐地址的技术在这里也很有用。
为了将大块内存清零,您还可以通过将范围分成多个部分并并行处理每个部分(其中部分的数量与核心/硬件线程的数量相同)来获得速度提升。
最重要的是,除非您尝试一下,否则无法判断这些是否有帮助。至少,看看你的编译器针对每种情况发出了什么。看看其他编译器为其标准“memset”发出了什么(它们的实现可能比您的编译器更有效)。
除非您有特定需求,或者知道您的编译器/ stdlib很烂,否则请坚持使用memset。它是通用的,总体上应该具有不错的性能。同样,编译器可能更容易优化/内联memset(),因为它可以对此提供内在支持。
例如,Visual C ++通常会生成内联版本的memcpy / memset,该内联版本的大小与对库函数的调用一样小,从而避免了push / call / ret开销。当可以在编译时评估size参数时,还有其他可能的优化方法。
就是说,如果您有特定的需求(尺寸总是很小 或* 很大),则可以通过降低到装配级别来提高速度。例如,使用直写操作将大量内存清零而不污染L2缓存。
但这全都取决于-对于普通的东西,请坚持使用memset / memcpy :)