零初始化器比 memset 快吗?

Posted

技术标签:

【中文标题】零初始化器比 memset 快吗?【英文标题】:Are zero initializers faster than memset? 【发布时间】:2016-11-24 12:24:29 【问题描述】:

我维护遗留的 C 代码,在许多地方他们都有像 int a[32]; 这样的小数组,然后是 memset(a, 0, sizeof a); 以将其初始化为零。

我正在考虑将其重构为 int a[32] = 0; 并删除 memset。

问题是:使用零初始值设定项通常会比调用 memset 更快吗?

【问题讨论】:

这是平台相关问题。 视情况而定。编译器可能在编译时为both memset 和初始化进行归零。或者它可以在运行时使用memset 进行初始化。您只需构建(通过优化)并查看生成的代码。 你的分析器说了什么?更重要的是,对于整数以外的类型,它们可能不同,memset 可能不会像程序员在某些平台上所期望的那样。 只要不慢,我真的推荐这样的改变。换句话说,即使生成的代码仍然调用memset(),您的解决方案也更好,因为它更高级。此外,使用不带括号的 sizeof a 的遗留代码给我们留下了深刻的印象! @unwind,这不是“传统”,而是强调大小取自对象而非类型的好习惯。 【参考方案1】:

TL;DR:使用初始化器 - 它永远不会比 memset() 更糟糕。

这取决于您的编译器。 它不应该比调用memset()(因为调用memset() 是编译器可用的一种选择)。

初始化器比强制覆盖数组更容易阅读;如果元素类型更改为您不想要的全位零,它也能很好地适应。


作为一个实验,让我们看看 GCC 对此做了什么:

#include <string.h>

int f1()

    int a[32] = 0;
    return a[31];


int f2()

    int a[32];
    memset(a, 0, sizeof a);
    return a[31];

使用gcc -S -std=c11 编译得到:

f1:
.LFB0:
    .file 1 "40786375.c"
    .loc 1 4 0
    .cfi_startproc
    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register 6
    subq    $8, %rsp
    .loc 1 5 0
    leaq    -128(%rbp), %rdx
    movl    $0, %eax
    movl    $16, %ecx
    movq    %rdx, %rdi
    rep stosq
    .loc 1 6 0
    movl    -4(%rbp), %eax
    .loc 1 7 0
    leave
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc
f2:
.LFB1:
    .loc 1 10 0
    .cfi_startproc
    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register 6
    addq    $-128, %rsp
    .loc 1 12 0
    leaq    -128(%rbp), %rax
    movl    $128, %edx
    movl    $0, %esi
    movq    %rax, %rdi
    call    memset@PLT
    .loc 1 13 0
    movl    -4(%rbp), %eax
    .loc 1 14 0
    leave
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc

表明f1() 使用rep stosq 作为初始化程序,而f2() 具有函数调用,与C 代码完全相同。 memset() 很可能对大型数组有更有效的矢量化实现,但对于像这样的小型数组,任何好处都可能被函数调用开销所抵消。

如果我们将a 声明为volatile,我们将看到启用优化后会发生什么 (gcc -S -std=c11 -O3):

f1:
.LFB4:
    .cfi_startproc
    subq    $16, %rsp
    .cfi_def_cfa_offset 24
    xorl    %eax, %eax
    movl    $16, %ecx
    leaq    -120(%rsp), %rdi
    rep stosq
    movl    4(%rsp), %eax
    addq    $16, %rsp
    .cfi_def_cfa_offset 8
    ret
    .cfi_endproc
f2:
.LFB5:
    .cfi_startproc
    subq    $16, %rsp
    .cfi_def_cfa_offset 24
    xorl    %eax, %eax
    movl    $16, %ecx
    leaq    -120(%rsp), %rdx
    movq    %rdx, %rdi
    rep stosq
    movl    4(%rsp), %eax
    addq    $16, %rsp
    .cfi_def_cfa_offset 8
    ret
    .cfi_endproc

您可以看到这两个函数现在编译为相同的代码。

【讨论】:

memset() 在大多数编译器中都是固有的。它在海湾合作委员会中。所以这并不能证明什么。 @Hans,我的编辑与您的评论交叉。我希望我的答案现在更清楚了。没有优化,初始化器版本是内联的;通过优化,这两个示例生成相同的机器代码。

以上是关于零初始化器比 memset 快吗?的主要内容,如果未能解决你的问题,请参考以下文章

使用 memset 初始化整数数组而不是像 array[10]=0 这样的默认零初始化是不是有任何性能优势?

使用 memset 将结构体数组及其成员初始化为零

零基础学C语言内存知识总结:memset函数和calloc函数

c语言memset()函数

结构零初始化方法

C语言中,使用一个结构体之前,要用memset把各个位清零???