所以我对两种不同代码技术的性能有疑问。你能帮我理解哪个更快/更好吗?为什么?
这是第一种技术:
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/