访问成员变量与局部变量的C ++性能

时间:2008-10-26 20:08:16

标签: c++ performance

类是否更有效地访问成员变量或局部变量?例如,假设您有一个(回调)方法,其唯一的职责是接收数据,对其执行计算,然后将其传递给其他类。在性能方面,有一个成员变量列表更有意义,该方法在接收数据时填充?或者只是在每次调用回调方法时声明局部变量?

假设这种方法每秒会被调用数百次......

如果我不清楚,这里有一些简单的例子:

// use local variables
class thisClass {
    public:
        void callback( msg& msg )
        {
            int varA;
            double varB;
            std::string varC;
            varA = msg.getInt();
            varB = msg.getDouble();
            varC = msg.getString();

            // do a bunch of calculations
         }

};

// use member variables
class thisClass {
    public:
        void callback( msg& msg )
        {
             m_varA = msg.getInt();
             m_varB = msg.getDouble();
             m_varC = msg.getString();

             // do a bunch of calculations
        }

    private:
        int m_varA;
        double m_varB;
        std::string m_varC;

};

11 个答案:

答案 0 :(得分:45)

执行摘要:在几乎所有场景中,无关紧要,但局部变量略有优势。

警告:您正在进行微观优化。你最终会花费数小时试图理解本应该赢得纳秒的代码。

警告:在你的场景中,性能不应该是问题,而是变量的作用 - 它们是临时的,还是thisClass的状态?

警告:优化的第一,第二和最后一条规则:措施!


首先,看看为x86生成的典型程序集(您的平台可能会有所不同):

// stack variable: load into eax
mov eax, [esp+10]

// member variable: load into eax
mov ecx, [adress of object]
mov eax, [ecx+4]

加载对象的地址后,在寄存器中,指令是相同的。加载对象地址通常可以与之前的指令配对,并且不会达到执行时间。

但这意味着ecx寄存器不可用于其他优化。 然而,现代CPU做了一些强烈的诡计,以减少问题。

此外,当访问许多对象时,这可能会花费额外的费用。 然而,这不到一个周期平均值,并且配对指令通常有更多的机会。

记忆位置:这是筹码赢得大时间的机会。堆栈顶部几乎总是在L1缓存中,因此负载需要一个周期。该对象更有可能被推回到L2缓存(经验法则,10个周期)或主存储器(100个周期)。

然而,您只需为首次访问付费。如果只有一次访问,则10或100个循环是不可察觉的。如果你有成千上万的访问,对象数据也将在L1缓存中。

总之,增益非常小,以至于将成员变量复制到本地以实现更好的性能几乎没有意义。

答案 1 :(得分:6)

我更喜欢局部变量的一般原则,因为它们可以最大限度地减少程序中的恶变状态。至于性能,您的探查器会告诉您需要知道的所有信息。对于整数和其他内置函数,本地应该更快,因为它们可以放在寄存器中。

答案 2 :(得分:4)

这应该是您的编译器问题。相反,优化可维护性:如果信息仅在本地使用,则将其存储在本地(自动)变量中。我讨厌阅读那些散布着成员变量的类,这些变量实际上并没有告诉我关于类本身的任何信息,而只讨论了一堆方法如何协同工作的一些细节:(

事实上,如果局部变量不是更快,我会感到惊讶 - 它们必然会在缓存中,因为它们接近其余的函数数据(调用帧),并且对象指针可能完全在某处别的 - 但我只是在这里猜测。

答案 3 :(得分:4)

愚蠢的问题。
这一切都取决于编译器及其对优化的作用。

即使它确实有效,你获得了什么?如何模糊你的代码?

变量访问通常通过指针和偏移完成。

  • 指向对象的指针+偏移量
  • 指向堆栈帧的指针+偏移量

另外,不要忘记添加将变量移动到本地存储的成本,然后再将结果复制回来。所有这些都可能意义不大,因为编译器可能足够聪明,无论如何都可以优化其中的大部分。

答案 4 :(得分:2)

其他人未明确提及的几点:

  • 您可能在代码中调用赋值运算符。 例如varC = msg.getString();

  • 每次设置功能框架时都会浪费一些周期。您正在创建变量,调用默认构造函数,然后调用赋值运算符以将RHS值引入本地。

  • 将locals声明为const-refs,当然也要初始化它们。

  • 成员变量可能在堆上(如果你的对象是在那里分配的),因此会受到非本地化的影响。

  • 即使节省了几个周期也是好的 - 如果可以避免的话,为什么要浪费计算时间。

答案 5 :(得分:1)

如有疑问,请自行查看基准测试。并确保它首先发挥作用 - 每秒数百次并不是现代处理器的巨大负担。

那就是说,我认为没有任何区别。两者都是指针的常量偏移量,本地人将来自堆栈指针,成员将来自“this”指针。

答案 6 :(得分:1)

在我的意见中,它不应该影响性能,因为:

  • 在您的第一个示例中,通过堆栈上的查找来访问变量,例如 [ESP] +4 表示当前堆栈结束加上四个字节
  • 在第二个示例中,通过相对于此的查找来访问变量(请记住,varB等于this-> varB)。这是一个类似的机器指令。

因此,没有太大区别。

但是,你应该避免复制字符串;)

答案 7 :(得分:1)

与您在算法实现中表示数据的方式相比,您将与之交互的数据量对执行速度的影响更大。

处理器并不真正关心数据是在堆栈上还是在堆上(除了堆栈顶部可能像处理器中提到的那样处理器缓存)但是为了获得最大速度,数据将具有适合处理器的缓存(L1缓存,如果你有多个级别的缓存,几乎所有现代处理器都有)。来自L2缓存的任何负载 - 或者$ DEITY禁止主内存 - 将减慢执行速度。因此,如果您正在处理一个大小为几百KB且每次调用几率的字符串,那么这种差异甚至无法衡量。

请记住,在大多数情况下,程序中10%的加速几乎无法被最终用户检测到(除非您设法将过夜批次的运行时间从25小时减少到不到24小时)所以这不是值得烦恼,除非你确定并且有探查器输出来备份这个特定的代码片段在10%-20%的“热区”内,这对你的程序的运行时有重大影响。

其他考虑因素应该更为重要,例如可维护性或其他外部因素。例如,如果上面的代码是大量多线程代码,那么使用局部变量可以使实现更容易。

答案 8 :(得分:1)

这取决于,但我希望绝对没有区别。

重要的是:使用成员变量作为临时代码会使代码不可重入 - 例如,如果两个线程试图在同一个对象上调用callback(),它将失败。使用静态局部变量(或静态成员变量)更糟糕,因为如果两个线程试图在任何 thisClass对象 - 或后代上调用callback(),则代码将失败。

答案 9 :(得分:0)

使用成员变量应该稍微快一些,因为它们只需要分配一次(构造对象时),而不是每次调用回调时。但与你可能正在做的其他工作相比,我预计这将是一个非常小的百分比。 Benckmark两者都看得更快。

答案 10 :(得分:0)

此外,还有第三种选择:静态本地人。每次调用函数时都不会重新分配这些函数(实际上,它们会在调用之间保留)但它们不会使用过多的成员变量污染类。