int x = 5;
int& y = x;
y += 10;
如果我们不使用引用,它实际上需要更多的周期吗?
int x = 5;
x += 10;
换句话说,机器码是否会改变,还是“别名”只在编译器级别发生?
这可能看起来像一个愚蠢的问题,但我很好奇。特别是在某些情况下,也许临时重命名一些成员变量只是为了让数学代码更容易阅读。当然,我们不是在讨论瓶颈...但这是我正在做的事情,所以我只是想知道是否有任何'真正'的区别...还是只是表面上的差异。
int x = 5;
int& y = x;
y += 10;
如果我们不使用引用,它实际上需要更多的周期吗?
int x = 5;
x += 10;
换句话说,机器码是否会改变,还是“别名”只在编译器级别发生?
这可能看起来像一个愚蠢的问题,但我很好奇。特别是在某些情况下,也许临时重命名一些成员变量只是为了让数学代码更容易阅读。当然,我们不是在讨论瓶颈...但这是我正在做的事情,所以我只是想知道是否有任何'真正'的区别...还是只是表面上的差异。
它可以被视为别名,但不是在效率上。在底层,引用是具有更好的语法和更高安全保证的指针。因此,你会有一个“解引用”操作的运行时惩罚,除非编译器进行了优化,但通常不能依靠这一点。
如果问题是编译器是否会进行优化,那么除了查看生成的汇编代码之外,没有其他方法。
确实,在大多数情况下,引用实现了“别名”这一概念,即对象的另一个名称。
但是,通常情况下,引用是通过指针实现的。然而,一个好的编译器只有在实际绑定是在运行时确定时才会使用实际指针来实现引用。如果绑定在编译时已知(并且类型匹配),编译器通常会将引用实现为相同对象的另一个名称,这种情况下通过引用访问对象与通过原始名称访问对象相比没有性能损失。
您的示例就是其中之一,您不应该期望从引用中获得性能损失。
这两个函数在 g++
中编译出的代码完全相同,即使只使用 -O1
。 (我添加了 return
语句以确保计算未被完全消除。)
没有指针,只有引用。在这个简单的例子中,没有性能差异。但不能保证对于所有引用的使用都没有性能差异。
int f()
{
int x = 5;
x += 10;
return x;
}
.
int f()
{
int x = 5;
int & y = x;
y += 10;
return y;
}
汇编语言:
movl $15, %eax
ret
我在Gnu/Linux上比较了两个程序。下面只显示了GCC的输出结果,但是clang的结果得出了相同的结论。
GCC版本:4.9.2
Clang版本:3.4.2
1.cpp
#include <stdio.h>
int main()
{
int x = 3;
printf("%d\n", x);
return 0;
}
2.cpp
#include <stdio.h>
int main()
{
int x = 3;
int & y = x;
printf("%d\n", y);
return 0;
}
尝试 1:没有优化
gcc -S --std=c++11 1.cpp
gcc -S --std=c++11 2.cpp
1.cpp 的汇编代码更短。
尝试 2:开启优化
gcc -S -O2 --std=c++11 1.cpp
gcc -S -O2 --std=c++11 2.cpp
生成的汇编代码完全相同。
1.cpp,无优化
.file "1.cpp"
.section .rodata
.LC0:
.string "%d\n"
.text
.globl main
.type main, @function
main:
.LFB0:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
subq $16, %rsp
movl $3, -4(%rbp)
movl -4(%rbp), %eax
movl %eax, %esi
movl $.LC0, %edi
movl $0, %eax
call printf
movl $0, %eax
leave
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size main, .-main
.ident "GCC: (Debian 4.9.2-10) 4.9.2"
.section .note.GNU-stack,"",@progbits
2.cpp,无优化
.file "2.cpp"
.section .rodata
.LC0:
.string "%d\n"
.text
.globl main
.type main, @function
main:
.LFB0:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
subq $16, %rsp
movl $3, -12(%rbp)
leaq -12(%rbp), %rax
movq %rax, -8(%rbp)
movq -8(%rbp), %rax
movl (%rax), %eax
movl %eax, %esi
movl $.LC0, %edi
movl $0, %eax
call printf
movl $0, %eax
leave
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size main, .-main
.ident "GCC: (Debian 4.9.2-10) 4.9.2"
.section .note.GNU-stack,"",@progbits
1. 使用优化后的cpp
.file "1.cpp"
.section .rodata.str1.1,"aMS",@progbits,1
.LC0:
.string "%d\n"
.section .text.unlikely,"ax",@progbits
.LCOLDB1:
.section .text.startup,"ax",@progbits
.LHOTB1:
.p2align 4,,15
.globl main
.type main, @function
main:
.LFB12:
.cfi_startproc
subq $8, %rsp
.cfi_def_cfa_offset 16
movl $3, %esi
movl $.LC0, %edi
xorl %eax, %eax
call printf
xorl %eax, %eax
addq $8, %rsp
.cfi_def_cfa_offset 8
ret
.cfi_endproc
.LFE12:
.size main, .-main
.section .text.unlikely
.LCOLDE1:
.section .text.startup
.LHOTE1:
.ident "GCC: (Debian 4.9.2-10) 4.9.2"
.section .note.GNU-stack,"",@progbits
2.cpp,进行优化
.file "1.cpp"
.section .rodata.str1.1,"aMS",@progbits,1
.LC0:
.string "%d\n"
.section .text.unlikely,"ax",@progbits
.LCOLDB1:
.section .text.startup,"ax",@progbits
.LHOTB1:
.p2align 4,,15
.globl main
.type main, @function
main:
.LFB12:
.cfi_startproc
subq $8, %rsp
.cfi_def_cfa_offset 16
movl $3, %esi
movl $.LC0, %edi
xorl %eax, %eax
call printf
xorl %eax, %eax
addq $8, %rsp
.cfi_def_cfa_offset 8
ret
.cfi_endproc
.LFE12:
.size main, .-main
.section .text.unlikely
.LCOLDE1:
.section .text.startup
.LHOTE1:
.ident "GCC: (Debian 4.9.2-10) 4.9.2"
.section .note.GNU-stack,"",@progbits
在优化后的GCC输出中,不存在运行时成本。同样适用于clang(测试版本为3.4.2):当开启优化时,生成的汇编代码在两个程序中是相同的。
是的,解引用引用后面的指针会产生额外的运行时成本,但可能微不足道。以最清晰易读和最清晰地表达你所追求的语义的方式编写代码,如果性能是一个问题,则在分析器中运行(瓶颈很少是你猜测的那样)。如果您使用的是MacOS,则Shark非常棒。