memset()是否比C中的for循环更有效?
对于循环,memset()是否比C中的for循环更有效?,c,performance,memset,C,Performance,Memset,对于循环,memset()比更有效 考虑到这一准则: char x[500]; memset(x,0,sizeof(x)); 还有这个: char x[500]; for(int i = 0 ; i < 500 ; i ++) x[i] = 0; charx[500]; 对于(inti=0;i
memset()
比更有效
考虑到这一准则:
char x[500];
memset(x,0,sizeof(x));
还有这个:
char x[500];
for(int i = 0 ; i < 500 ; i ++) x[i] = 0;
charx[500];
对于(inti=0;i<500;i++)x[i]=0;
哪一个更有效?为什么?硬件中是否有任何特殊指令来执行块级初始化。这实际上取决于编译器和库。对于较旧的编译器或简单的编译器,memset可以在库中实现,并且不会比自定义循环执行得更好
对于几乎所有值得使用的编译器,memset是一个内在函数,编译器将为其生成优化的内联代码
其他人建议进行分析和比较,但我不介意。只需使用memset。代码简单易懂。在您的基准测试告诉您这部分代码是性能热点之前,不要担心这一点。最肯定的是,memset
将比该循环快得多。请注意如何一次处理一个字符,但这些函数经过优化,一次可以设置几个字节,甚至可以使用MMX和SSE指令(如果可用)
我认为这些优化的范例是GNUClibrarystrlen
函数,通常不会被注意到。有人会认为它至少有O(n)个性能,但实际上它有O(n/4)或O(n/8),这取决于体系结构(是的,我知道,在big中,O()将是相同的,但实际上有八分之一的时间)。怎么用?棘手,但很好:。答案是“视情况而定”memset
可能更有效,也可能在内部使用for循环。我想不出memset
的效率会降低。在这种情况下,它可能会变成一个更有效的for循环:循环迭代500次,每次将数组的字节数设置为0。在64位机器上,您可以循环,一次设置8个字节(一个长字节),这几乎快8倍,最后只处理剩余的4个字节(500%8)
编辑:
事实上,memset
在glibc中就是这样做的:
正如Michael指出的,在某些情况下(数组长度在编译时已知),C编译器可以内联memset
,从而消除函数调用的开销。Glibc还为大多数主要平台提供了组件优化版的memset
,如amd64:
好的编译器会识别for循环,并用最佳内联序列或对memset的调用来替换它。当缓冲区大小较小时,它们还将用最佳内联序列替换memset
在实践中,使用优化编译器生成的代码(因此性能)将是相同的。同意上述观点。视情况而定。但是,可以肯定的是,memset比for循环更快或相等。如果您不确定您的环境或懒得进行测试,请选择安全路线,使用memset。好吧,我们为什么不看看生成的汇编代码,VS 2010下的完全优化
char x[500];
char y[500];
int i;
memset(x, 0, sizeof(x) );
003A1014 push 1F4h
003A1019 lea eax,[ebp-1F8h]
003A101F push 0
003A1021 push eax
003A1022 call memset (3A1844h)
还有你的循环
char x[500];
char y[500];
int i;
for( i = 0; i < 500; ++i )
{
x[i] = 0;
00E81014 push 1F4h
00E81019 lea eax,[ebp-1F8h]
00E8101F push 0
00E81021 push eax
00E81022 call memset (0E81844h)
/* note that this is *replacing* the loop,
not being called once for each iteration. */
}
charx[500];
chary[500];
int i;
对于(i=0;i<500;++i)
{
x[i]=0;
00E81014推送1F4h
00E81019 lea eax,[ebp-1F8h]
00E8101F推送0
00E81021推送式eax
00E81022呼叫成员集(0E81844h)
/*请注意,这是“替换”循环,
每次迭代不调用一次*/
}
因此,在这个编译器下,生成的代码是完全相同的memset
速度很快,而且编译器足够聪明,知道您正在做的事情与调用memset
一次是一样的,所以它会为您做这件事
如果编译器实际上让循环保持原样,那么它可能会变慢,因为一次可以设置多个字节大小的块(也就是说,您可以至少展开一点循环。您可以假设memset
的速度至少与原始实现(如循环)一样快。在调试构建下尝试,您会注意到循环没有被替换
这就是说,这取决于编译器为您做了什么。查看反汇编始终是准确了解发生了什么的好方法。还可以使用其他技术,如减少循环数。memset()的代码可以模仿著名的:
void*duff\u memset(char*to,int c,size\u t count)
{
尺寸;
char*p=to;
n=(计数+7)/8;
开关(计数%8){
案例0:do{*p++=c;
案例7:*p++=c;
案例6:*p++=c;
案例5:*p++=c;
案例4:*p++=c;
案例3:*p++=c;
案例2:*p++=c;
案例1:*p++=c;
}而(-n>0);
}
返回;
}
过去,这些技巧用于提高执行速度。但在现代体系结构中,这往往会增加代码大小并增加缓存未命中率
因此,很难说哪种实现更快,因为这取决于编译器优化的质量、C库利用特殊硬件指令的能力、操作的数据量以及底层操作系统的功能(页面错误管理、TLB未命中、写时复制)
例如,在glibc中,memset()以及各种其他“copy/set”函数(如bzero()或strcpy())的实现是否依赖于体系结构来利用各种优化的硬件指令,如或。是。否。可能。这取决于。获得有用答案的唯一方法是在您的环境中对其进行分析和分析。在我的编译器、我的程序和我的计算机上,哪一个更快,告诉您没有任何有用的信息。为什么要费心调查?除非有数据是否显示了其他方面(您没有达到性能目标,调查指向这部分代码),这段代码可能不是热点,您应该