零初始化比memset更快吗?

5

我维护一些遗留的 C 代码,在很多地方都有像 int a[32]; 这样的小数组,后面跟着一个 memset(a, 0, sizeof a); 来进行零初始化。

我想将其重构成 int a[32] = {0}; 的形式,并删除 memset。

问题是:使用零初始化器通常比调用 memset 更快吗?


2
这是一个依赖于平台的问题。 - user694733
3
视情况而定。编译器可能会在编译时为memset和初始化都执行清零操作,也可能使用memset在运行时执行初始化。您只需要进行(优化后的)构建并查看生成的代码即可。 - Some programmer dude
3
你的分析工具说了什么?更重要的是,对于除整数以外的其他类型,它们可能会有所不同,并且在某些平台上,“memset”可能不会做程序员期望的事情。 - too honest for this site
4
只要不慢,我真的建议这样更改。换句话说,即使生成的代码仍然调用memset(),您的解决方案也更好,因为它更高级。此外,对于使用没有括号的 sizeof a 的遗留代码印象非常深刻! - unwind
@unwind,这不是“遗留问题”,而是强调取对象的大小而不是类型的良好实践。 - Jens Gustedt
显示剩余4条评论
1个回答

4

简而言之:使用初始化器 - 它永远不会比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()是大多数编译器中的内置函数。它在GCC中也是如此。所以这并不能证明什么。 - Hans Passant
@Hans,我刚编辑时看到了你的评论。希望我的答案现在更清晰了。没有优化的情况下,初始化程序版本将被内联;有了优化,这两个示例将生成相同的机器代码。 - Toby Speight

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接