返回私有类成员的速度是否比使用结构并直接访问该变量慢

Is returning a private class member slower than using a struct and accessing that variable directly?

本文关键字:结构 访问 变量 成员 是否 速度 返回      更新时间:2023-10-16

假设你有一个类,它有私有成员,这些成员在程序中被大量访问(例如在一个必须快速的循环中(。想象一下,我定义了这样的东西:

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(?

他们应该是一样的。还记得您尝试在矢量上使用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

如您所见,如果没有优化,结构比访问器更快,但是谁在没有优化的情况下交付代码呢?

您可以期待相同的性能。 许多C++类都依赖于此 - 例如,C++11 的list::size() const可以预期会简单地返回数据成员。 (这与vector()形成鲜明对比,其中的实现我看过将size()计算为对应于begin()end()的指针数据成员之间的差异,如果优化器无法确定size()在循环迭代中是恒定的,则确保典型的迭代器使用尽可能快,代价是索引迭代可能变慢(。

对于像 unsigned 这样的类型,通常没有特别的理由通过引用const返回,无论如何都应该适合 CPU 寄存器,但由于它是内联的,编译器不必从字面上理解这一点(对于外联版本,它可能会通过返回必须取消引用的指针来实现(。 (非典型的原因是允许获取变量的地址,这就是为什么说vector::operator[](size_t) const需要返回一个const T&而不是一个T,即使T足够小以适合寄存器。

只有一种方法可以确定在您的特定程序中,哪一个更快,在您的特定程序中,使用您的特定工具在您的特定平台上使用您的特定优化标志 - 通过测量两个变体。

话虽如此,二进制文件很有可能是相同的,指令对指令。

正如其他人所说,如今的优化器被用来浓缩抽象(特别是在C++中,它或多或少是为了利用这一点而构建的(,它们非常非常好。

但是您可能不需要为此使用吸气剂。

struct Foo {
    Foo(unsigned set) : vari(set) {}
    unsigned const vari;
};

const并不禁止初始化。