memset()比C中的循环效率更高吗?

时间:2011-09-09 21:32:51

标签: 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;

哪一个更有效率,为什么?是否有任何特殊的硬件指令来进行块级初始化。

8 个答案:

答案 0 :(得分:31)

那么,为什么我们不看看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. */
}

因此,在此编译器下,生成的代码完全相同。 memset速度很快,编译器很聪明,知道你做的事情和调用memset一样,所以它会为你做。

如果编译器实际上按原样离开了循环,那么它可能会慢一些,因为你可以一次设置多个字节大小的块(也就是说,你可以在最小程度上展开你的循环。你可以假设memset 至少与一个天真的实现(例如循环)一样快。在调试版本下尝试它,你会注意到循环没有被替换。

那说,这取决于编译器为你做什么。查看反汇编始终是确切知道发生了什么的好方法。

答案 1 :(得分:25)

当然,memset会比那个循环快得多。请注意您一次如何处理一个字符,但这些功能经过优化,一次设置几个字节,即使使用MMX和SSE指令也是如此。

我认为这些优化的典型示例(通常不被注意)是GNU C库strlen函数。人们会认为它至少具有O(n)性能,但它实际上具有O(n / 4)或O(n / 8),具体取决于体系结构(是的,我知道,在大O()中将是相同的,但实际上你得到的是第八。怎么样?很狡猾,但很好:strlen

答案 2 :(得分:12)

这实际上取决于编译器和库。对于较旧的编译器或简单的编译器,memset可能在库中实现,并且性能不如自定义循环。

对于几乎所有值得使用的编译器,memset是一个内部函数,编译器将为它生成优化的内联代码。

其他人建议进行剖析和比较,但我不会打扰。只需使用memset。代码简单易懂。在您的基准测试告诉您这部分代码是性能热点之前,请不要担心。

答案 3 :(得分:8)

答案是'这取决于'。 memset可能更有效,或者它可能在内部使用for循环。我想不出memset效率会降低的情况。在这种情况下,它可能会变成一个更有效的for循环:你的循环迭代500次,每次将数组的字节值设置为0。在64位机器上,你可以循环,一次设置8个字节(一个很长的长),这几乎要快8倍,并且最后只处理剩余的4个字节(500%8)。

编辑:

实际上,这是memset在glibc中的作用:

http://repo.or.cz/w/glibc.git/blob/HEAD:/string/memset.c

正如Michael指出的那样,在某些情况下(编译时已知数组长度),C编译器可以内联memset,摆脱函数调用的开销。对于大多数主要平台,Glibc还具有memset的汇编优化版本,例如amd64:

http://repo.or.cz/w/glibc.git/blob/HEAD:/sysdeps/x86_64/memset.S

答案 4 :(得分:3)

好的编译器会识别for循环并将其替换为最佳内联序列或调用memset。当缓冲区大小很小时,它们还将用最佳内联序列替换memset。

实际上,使用优化编译器,生成的代码(以及性能)将是相同的。

答案 5 :(得分:2)

同意上述内容。这取决于。但是,确保memset更快或等于for循环。如果您不确定您的环境或懒得测试,请采取安全路线并使用memset。

答案 6 :(得分:1)

也可以使用其他技术,如 loop unrolling 减少循环次数。 memset() 的代码可以模仿著名的duff's device

void *duff_memset(char *to, int c, size_t count)
{
    size_t n;
    char *p = to;
    n = (count + 7) / 8;
    switch (count % 8) {
    case 0: do { *p++ = c;
    case 7:      *p++ = c;
    case 6:      *p++ = c;
    case 5:      *p++ = c;
    case 4:      *p++ = c;
    case 3:      *p++ = c;
    case 2:      *p++ = c;
    case 1:      *p++ = c;
            } while (--n > 0);
    }
    return to;
}

过去用于提高执行速度的那些技巧。但在现代架构上,这往往会增加代码大小并增加缓存未命中。

因此,很难说哪种实现更快,因为它取决于编译器优化的质量、C 库利用特殊硬件指令的能力、您正在操作的数据量以及底层操作系统的功能(页面错误管理、TLB 未命中、写时复制)。

例如,在 glibc 中,memset() 以及各种其他“复制/设置”函数的实现,如 bzero()strcpy () 依赖于架构,以利用各种优化的硬件指令,如 SSEAVX

答案 7 :(得分:-1)

void fill_array(void* array, size_t size_of_item, size_t length, void* value) {
  uint8_t* bytes      = value;
  uint8_t  first_byte = bytes[0];

  if (size_of_item == 1) {
    memset(array, first_byte, length);
    return;
  }

  // size_of_item > 1 here.
  bool all_bytes_are_identical = true;

  for (size_t byte_index = 1; byte_index < size_of_item; byte_index++) {
    if (bytes[byte_index] != first_byte) {
      all_bytes_are_identical = false;
      break;
    }
  }

  if (all_bytes_are_identical) {
    memset(array, first_byte, size_of_item * length);
    return;
  }

  for (size_t index = 0; index < length; index++) {
    memcpy((uint8_t*)array + size_of_item * index, value, size_of_item);
  }
}

memset效率更高,它不必关心非对称值(其中all_bytes_are_identicalfalse)。因此,您将搜索如何包装它。

这是我的变体。它适用于大小端系统。