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指令(如果可用)


我认为这些优化的范例是GNUClibrary
strlen
函数,通常不会被注意到。有人会认为它至少有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())的实现是否依赖于体系结构来利用各种优化的硬件指令,如或。

是。否。可能。这取决于。获得有用答案的唯一方法是在您的环境中对其进行分析和分析。在我的编译器、我的程序和我的计算机上,哪一个更快,告诉您没有任何有用的信息。为什么要费心调查?除非有数据是否显示了其他方面(您没有达到性能目标,调查指向这部分代码),这段代码可能不是热点,您应该