c - 在每次迭代中重新声明变量是否比在每次迭代后重置它们更快?

标签 c performance

所以我对两种不同代码技术的性能有疑问。你能帮我理解哪个更快/更好吗?为什么?

这是第一种技术:

int x, y, i;
for(i=0; i<10; i++)
{
    //do stuff with x and y
}
//reset x and y to zero
x=0; 
y=0;

这是第二个:

int i;
for(i=0; i<10; i++)
{
    int x, y;
    //do the same stuff with x and y as above
}

那么哪种编码技术更好呢? 此外,如果您知道更好的和/或任何网站/文章等,我可以在其中阅读有关此内容以及更多与性能相关的内容,我也很乐意拥有它!

最佳答案

这根本无关紧要,因为编译器不会自动将变量声明翻译成内存或寄存器分配。这两个示例之间的区别在于,在第一种情况下,变量在循环体外部可见,而在第二种情况下则不可见。然而,这种差异仅存在于 C 级别,如果您不在循环外使用变量,则会产生相同的编译代码。

对于局部变量的存储位置,编译器有两种选择:要么在堆栈上,要么在寄存器中。对于您在程序中使用的每个变量,编译器必须选择它的位置。如果在堆栈上,则需要递减堆栈指针以为变量腾出空间。但是这种递减不会发生在变量声明的地方,通常它会在函数的开头完成:堆栈指针只会递减一次足以容纳所有堆栈分配变量的量。如果它只是在寄存器中,则不需要进行初始化,并且在您第一次进行分配时寄存器将用作目标。重要的是它可以并且将会重新使用以前用于现在超出范围的变量的内存位置和寄存器。

为了说明,我制作了两个测试程序。我使用了 10000 次迭代而不是 10 次,否则编译器将在高优化级别展开循环。这些程序使用 rand 来制作快速且可移植的演示,但不应在生产代码中使用它。

声明一次.c :

#include <stdio.h>
#include <time.h>
#include <stdlib.h>

int main(void) {
    srand(time(NULL));

    int x, y, i;
    for (i = 0; i < 10000; i++) {
        x = rand();
        y = rand();
        printf("Got %d and %d !\n", x, y);
    }

    return 0;
}

redeclare.c 除了循环是一样的:

for (i = 0; i < 10000; i++) {
    int x, y;
    x = rand();
    y = rand();
    printf("Got %d and %d !\n", x, y);
}

我在 x86_64 Mac 上使用 Apple 的 LLVM 7.3.0 版编译程序。我要求它提供我在下面复制的汇编输出,省略了与问题无关的部分。

clang -O0 -S declare_once.c -o declare_once.S :

_main:
## Function prologue
    pushq   %rbp
    movq    %rsp, %rbp           ## Move the old value of the stack 
                                 ## pointer (%rsp) to the base pointer 
                                 ## (%rbp), which will be used to 
                                 ## address stack variables

    subq    $32, %rsp            ## Decrement the stack pointer by 32 
                                 ## to make room for up to 32 bytes 
                                 ## worth of stack variables including 
                                 ## x and y

## Removed code that calls srand

    movl    $0, -16(%rbp)        ## i = 0. i has been assigned to the 4 
                                 ## bytes starting at address -16(%rbp),
                                 ## which means 16 less than the base  
                                 ## pointer (so here, 16 more than the 
                                 ## stack pointer).

LBB0_1:                                 
    cmpl    $10, -16(%rbp)
    jge LBB0_4
    callq   _rand                ## Call rand. The return value will be in %eax

    movl    %eax, -8(%rbp)       ## Assign the return value of rand to x. 
                                 ## x has been assigned to the 4 bytes
                                 ## starting at -8(%rbp)
    callq   _rand
    leaq    L_.str(%rip), %rdi
    movl    %eax, -12(%rbp)      ## Assign the return value of rand to y. 
                                 ## y has been assigned to the 4 bytes
                                 ## starting at -12(%rbp)
    movl    -8(%rbp), %esi
    movl    -12(%rbp), %edx
    movb    $0, %al
    callq   _printf
    movl    %eax, -20(%rbp)
    movl    -16(%rbp), %eax
    addl    $1, %eax
    movl    %eax, -16(%rbp)
    jmp LBB0_1
LBB0_4:
    xorl    %eax, %eax
    addq    $32, %rsp            ## Add 32 to the stack pointer : 
                                 ## deallocate all stack variables 
                                 ## including x and y
    popq    %rbp
    retq

redeclare.c 的汇编输出几乎完全相同,除了由于某些原因 x 和 y 被分配给 -16(%rbp)-12(%rbp) i 被分配给 -8(%rbp)。我只复制粘贴了循环:

    movl    $0, -16(%rbp)
LBB0_1:
    cmpl    $10, -16(%rbp)
    jge LBB0_4
    callq   _rand
    movl    %eax, -8(%rbp)        ## x = rand();
    callq   _rand
    leaq    L_.str(%rip), %rdi
    movl    %eax, -12(%rbp)       ## y = rand();
    movl    -8(%rbp), %esi
    movl    -12(%rbp), %edx
    movb    $0, %al
    callq   _printf
    movl    %eax, -20(%rbp)
    movl    -16(%rbp), %eax
    addl    $1, %eax
    movl    %eax, -16(%rbp)
    jmp LBB0_1

因此我们看到即使在 -O0 时生成的代码也是相同的。需要注意的重要一点是,在每次循环迭代中 x 和 y 重复使用相同的内存位置,即使从 C 语言的角度来看它们在每次迭代中都是单独的变量。

在 -O3 处,变量保存在寄存器中,两个程序输出完全相同的程序集

clang -O3 -S declare_once.c -o declare_once.S :

    movl    $10000, %ebx       ## i will be in %ebx. The compiler decided
                               ## to count down from 10000 because 
                               ## comparisons to 0 are less expensive,
                               ## so it actually does i = 10000.
    leaq    L_.str(%rip), %r14
    .align  4, 0x90
LBB0_1:
    callq   _rand
    movl    %eax, %r15d        ## x = rand(). x has been assigned to
                               ## register %r15d (32 less significant
                               ## bits of r15)
    callq   _rand
    movl    %eax, %ecx         ## y = rand(). y has been assigned to
                               ## register %ecx
    xorl    %eax, %eax
    movq    %r14, %rdi
    movl    %r15d, %esi
    movl    %ecx, %edx
    callq   _printf
    decl    %ebx
    jne LBB0_1

所以再次重申,两个版本之间没有区别,即使在 redeclare.c 中我们在每次迭代中都有不同的变量,相同的寄存器被重新使用,因此没有分配开销。

请记住,我所说的一切都适用于在每次循环迭代中分配的变量,这似乎就是您的想法。另一方面,如果您想对所有迭代使用相同的值,当然应该在循环之前完成赋值。

关于c - 在每次迭代中重新声明变量是否比在每次迭代后重置它们更快?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/37708617/

相关文章:

c++ - 从程序执行批处理文件

mysql - Grails/Hibernate 数据库在负载 : Unable to connect (even when pooling) 下崩溃

sql - 更新具有多列的表中的行

c - 在 C 中使用子进程的 exec 命令执行 bin 程序

C编程: how do you print the contents of a file after it's been dynamically allocated?

c - 使用 gtk c++ 更新多个进度条

c - 如何在 C 编程中取消引用多维数组?

C++读取大数据,解析,然后写入数据

c++ - 总是在 lambda 表达式中捕获所有内容是一种不好的做法吗?

algorithm - 在经过排序的整数数组中搜索,该数组会滚动