在C中使用memset()有什么好处

在C中使用memset()有什么好处,c,embedded,memset,C,Embedded,Memset,我很好奇,在与下面类似的情况下,使用memset()在效率方面是否有任何优势 给定以下缓冲区声明 struct More_Buffer_Info { unsigned char a[10]; unsigned char b[10]; unsigned char c[10]; }; struct My_Buffer_Type { struct More_Buffer_Info buffer_info[100]; }; struct My_Buffer_Type

我很好奇,在与下面类似的情况下,使用memset()在效率方面是否有任何优势

给定以下缓冲区声明

struct More_Buffer_Info
{
    unsigned char a[10];
    unsigned char b[10];
    unsigned char c[10];
};

struct My_Buffer_Type
{
    struct More_Buffer_Info buffer_info[100];
};

struct My_Buffer_Type my_buffer[5];

unsigned char *p;
p = (unsigned char *)my_buffer;
除了代码行数较少外,使用此选项是否有好处:

memset((void *)p, 0, sizeof(my_buffer));
在这方面:

for (i = 0; i < sizeof(my_buffer); i++)
{
    *p++ = 0;
}
for(i=0;i
这适用于
memset()
memcpy()

  • 代码更少:正如您已经提到的,它更短-代码行更少
  • 更具可读性:越短通常也会使其更具可读性。(
    memset()
    比该循环可读性更强)
  • 它可以更快:它有时允许更激进的编译器优化。(因此可能更快)
  • 未对齐:在某些情况下,当您在不支持未对齐访问的处理器上处理未对齐数据时,
    memset()
    memcpy()
    可能是唯一干净的解决方案
  • 为了扩展第三点,
    memset()
    可以由编译器使用SIMD等进行大量优化。如果改为编写循环,编译器将首先需要“弄清楚”它做了什么,然后才能尝试对其进行优化

    这里的基本思想是
    memset()
    和类似的库函数在某种意义上“告诉”编译器您的意图


    正如@Oli在评论中提到的,有一些缺点。我将在这里详细介绍:

  • 您需要确保
    memset()
    确实满足您的需求。标准并没有说各种数据类型的零在内存中一定是零
  • 对于非零数据,
    memset()
    仅限于1字节的内容。因此,如果要将
    int
    s数组设置为零以外的值(或
    0x01010101
    或其他值),则不能使用
    memset()
  • 虽然很少见,但也有一些特殊情况,实际上可以通过自己的循环在性能上击败编译器*
  • *根据我的经验,我将举一个例子:

    虽然
    memset()
    memcpy()
    通常是编译器内部函数,由编译器进行特殊处理,但它们仍然是泛型函数。他们没有提到数据类型,包括数据的对齐方式

    因此,在少数(极少数)情况下,编译器无法确定内存区域的对齐方式,因此必须生成额外的代码来处理未对齐。然而,如果你是程序员,100%确信对齐,那么使用循环实际上可能会更快

    一个常见的例子是使用SSE/AVX内部函数。(例如复制一个16/32字节对齐的浮点数组)如果编译器不能确定16/32字节对齐方式,它将需要使用未对齐的加载/存储和/或处理代码。如果您只是使用SSE/AVX对齐的加载/存储内部函数编写一个循环,您可能会做得更好

    float *ptrA = ...  //  some unknown source, guaranteed to be 32-byte aligned
    float *ptrB = ...  //  some unknown source, guaranteed to be 32-byte aligned
    int length = ...   //  some unknown source, guaranteed to be multiple of 8
    
    //  memcopy() - Compiler can't read comments. It doesn't know the data is 32-byte
    //  aligned. So it may generate unnecessary misalignment handling code.
    memcpy(ptrA, ptrB, length * sizeof(float));
    
    //  This loop could potentially be faster because it "uses" the fact that
    //  the pointers are aligned. The compiler can also further optimize this.
    for (int c = 0; c < length; c += 8){
        _mm256_store_ps(ptrA + c, _mm256_load_ps(ptrB + c));
    }
    
    float*ptrA=…//某些未知源,保证32字节对齐
    浮点*ptrB=…//某些未知源,保证32字节对齐
    整数长度=…//某些未知源,保证为8的倍数
    //memcopy()-编译器无法读取注释。它不知道数据是32字节
    //对齐。因此,它可能会生成不必要的未对准处理代码。
    memcpy(ptrA、ptrB、长度*尺寸(浮动));
    //这个循环可能会更快,因为它“使用”了以下事实
    //指针是对齐的。编译器还可以进一步优化这一点。
    对于(int c=0;c
    memset提供了一种编写代码的标准方法,让特定的平台/编译器库确定最有效的机制。例如,根据数据大小,它可以尽可能多地进行32位或64位存储。

    这取决于编译器和库的质量。在大多数情况下,memset是优越的

    memset的优势在于,在许多平台上,它实际上是一个;也就是说,编译器可以“理解”将大量内存设置为某个值的意图,并可能生成更好的代码

    特别是,这可能意味着使用特定的硬件操作来设置大内存区域,如x86上的SSE、PowerPC上的AltiVec、ARM上的NEON等等。这可能是一个巨大的性能改进

    另一方面,通过使用for循环,您告诉编译器执行更具体的操作,“将此地址加载到寄存器中。向其写入一个数字。向地址添加一个数字。向其写入一个数字”,依此类推。理论上,一个完全智能的编译器会识别出这个循环是什么,并将它转换成一个memset;但我从来没有遇到过真正的编译器可以做到这一点

    因此,假设memset是由聪明人编写的,是为编译器支持的特定平台和硬件设置整个内存区域的最佳和最快的方法。确实如此。

    有两个优点:

  • 带有
    memset
    的版本更易于阅读-这与代码行数较少有关,但与之不同。了解
    memset
    版本的功能不需要太多思考,尤其是编写它时

    memset(my_buffer, 0, sizeof(my_buffer));
    

    而不是间接地通过<代码> P<代码>和不必要的转换到 Value*/Cuff>(注释:如果你真的在C中编码而不是C++,那么不必要的话,有些人对这个区别不清楚)。

  • memset
    可能一次写入4或8个字节和/或利用特殊的缓存提示指令;因此,它可能比一次一个字节的循环快。(注意:有些编译器足够聪明,可以识别大容量清除循环,并替换对内存的更广泛写入或对
    memset的调用。)<
    
    for (i = 0; i < sizeof(my_buffer); i++)
    {
        p[i] = 0;
    }
    
    for (i = 0; i < sizeof(my_buffer); i++)
    {
        *p++ = 0;
    }
    
    memset( my_buffer, 0, sizeof(my_buffer));