假设您有一个具有私有成员的类,这些成员在程序中被大量访问(例如在一个必须快速的循环中)。想象一下,我已经定义了这样的东西:
class Foo
{
public:
Foo(unsigned set)
: vari(set)
{}
const unsigned& read_vari() const { return vari; }
private:
unsigned vari;
};
我想这样做的原因是,一旦创建了类,“vari”就不应再被更改了。因此,为了最大限度地减少错误发生,“当时似乎是一个好主意”。
但是,如果我现在需要数百万次调用此函数,我想知道是否存在开销和减速而不是简单地使用:
struct Foo
{
unsigned vari;
};
那么,这是我在使用类时的第一个影响因素,以避免任何人在构造函数设置后错误地更改变量的值? 此外,这是否以函数调用开销的形式引入“惩罚”。 (假设我在编译器中使用优化标志,例如GCC中的-O2)?
答案 0 :(得分:9)
它们应该是一样的。请记住,您尝试在向量上使用operator[]
并且gdb
刚刚回复optimized out
时遇到了令人沮丧的时间?这就是这里会发生的事情。编译器不会在这里创建函数调用,而是直接访问变量。
我们来看看下面的代码
struct foo{
int x;
int& get_x(){
return x;
}
};
int direct(foo& f){
return f.x;
}
int fnc(foo& f){
return f.get_x();
}
使用g++ test.cpp -o test.s -S -O2
编译的。 -S
标志告诉编译器“在编译阶段之后停止;不要汇编(从g ++手册页引用)”。这就是编译器给我们的:
_Z6directR3foo:
.LFB1026:
.cfi_startproc
movl (%rdi), %eax
ret
和
_Z3fncR3foo:
.LFB1027:
.cfi_startproc
movl (%rdi), %eax
ret
如您所见,在第二种情况下没有进行任何函数调用,它们都是相同的。这意味着使用访问器方法没有性能开销。
奖励:如果关闭优化会怎样?相同的代码,结果如下:
_Z6directR3foo:
.LFB1022:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
movq %rdi, -8(%rbp)
movq -8(%rbp), %rax
movl (%rax), %eax
popq %rbp
.cfi_def_cfa 7, 8
ret
和
_Z3fncR3foo:
.LFB1023:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
subq $16, %rsp
movq %rdi, -8(%rbp)
movq -8(%rbp), %rax
movq %rax, %rdi
call _ZN3foo5get_xEv #<<<call to foo.get_x()
movl (%rax), %eax
leave
.cfi_def_cfa 7, 8
ret
正如您所看到的那样,没有优化,sturct 比访问者更快,但谁发送的代码没有优化?
答案 1 :(得分:5)
您可以期待相同的性能。许多C ++类都依赖于此 - 例如,C ++ 11的list::size() const
可以简单地返回数据成员。 (与vector()
形成鲜明对比,其中我实现的实现计算size()
作为指针数据成员对应begin()
和end()
之间的差异,确保典型的迭代器用法是如果优化器无法确定size()
在循环迭代中是恒定的,那么尽可能快地以可能较慢的索引迭代为代价。
通常没有特别的理由通过const
引用返回类似unsigned
的类型,无论如何都应该适合CPU寄存器,但是因为内联编译器不需要按字面意思(对于一个外联版本,可能会通过返回一个必须被解除引用的指针来实现。 (非典型的原因是允许获取变量的地址,这就是为什么说vector::operator[](size_t) const
需要返回const T&
而不是T
,即使T
很小足以装入登记册。)
答案 2 :(得分:0)
只有一种方法可以肯定地告诉您在特定程序中使用特定平台上的特定优化标记构建的特定程序中哪一个更快 - 通过测量两种变种。
话虽如此,二进制文件可能是完全相同的,教学指导。
答案 3 :(得分:0)
正如其他人所说,现在优化者依赖于抽象化(特别是在C ++中,或多或少是为了利用它而构建的)并且它们非常非常好。
但你可能不需要吸气剂。
struct Foo {
Foo(unsigned set) : vari(set) {}
unsigned const vari;
};
const
禁止初始化。