C ++性能:局部变量vs数据成员

时间:2013-01-19 02:58:20

标签: c++

假设代码打击是我的班级。这是简化而不完整。让我们关注operator()的实现。

class Delta{
public:
    long long operator()() {
        auto now = steady_clock::now(); 
        auto delta = (now - last).count();
        last = now;
        return delta;
    }
private:
    steady_clock::time_point last;
};

operator()每秒可能被调用数千次。我只是想知道经常分配和释放变量nowdelta可能会损害operator()的性能。如果我想最大限度地提高速度,最好是nowdelta class Delta数据成员?但我也听说编译后局部变量甚至可能不存在。所以不知何故,开销也不存在。

实际上,这个运算符的速度对我的应用程序的速度没有任何影响。我只是想知道编译器中立的答案。当这种情况来临时。我应该将其设为数据成员还是局部变量?

5 个答案:

答案 0 :(得分:2)

在x86-64上,我希望这段代码最终会在RAX中分配nowdelta。在汇编语言中,代码看起来像这个顺序:

assume RSI:ptr _Delta
call steady_clock::now()
sub rax, [rsi].last
mov [rsi].last, rax
ret

当然,在实际的汇编语言中,你会看到steady_clock::now()的错位名称(例如),但你会得到一般的想法。在进入任何非静态成员函数时,它将在某个寄存器中具有this。返回值始终为rax。我没有看到编译器需要(甚至想要)为任何其他变量分配空间的任何特别好的理由。

在32位x86上,最终使用一些堆栈空间的可能性要高得多,尽管它可能会在EDX中返回64位值:EAX,在这种情况下,事情最终会非常相似如上所述,只需再使用一个寄存器。

大多数其他处理器的开始时寄存器数多于x86,因此寄存器压力较低。例如,在SPARC上,例程通常以8个本地寄存器开始并且可以使用,因此在寄存器中分配now几乎是确定的。

底线:你不太可能看到显着的速度差异,但如果你确实看到了差异,我猜它更倾向于使用局部变量而不是成员变量。

答案 1 :(得分:1)

它不会产生太多(如果有的话)差异。 OS根据页面分配内存(包括堆栈)。因此,堆栈可能无法完成页面,因此该过程不需要上下文切换来获取另一页面。

至于编译器中立的答案,速度将归结为上下文切换,处理器上运行的其他事情,....

除了像你这样的人似乎专注于微观性能改进,但避免更大的图景。最好首先找出瓶颈的第一个位置并专注于那些。请记住80/20规则。

答案 2 :(得分:1)

优化通常取决于编译器。但假设你使用的是一些不错的编译器,就不会有性能损失,所以不用担心。为了证明这一点,我用gcc 4.7编译了你的代码,优化级别3:

call   400770 <std::chrono::system_clock::now()@plt> ;; Call.
mov    rdx,rax             ;; Remembe temporary value in %rdx.
sub    rax,QWORD PTR [rbx] ;; Divide
mov    QWORD PTR [rbx],rdx ;; Wrie Back.

根据具体情况,可能会进一步优化。或者它可能会变得更糟。只是为了举例说明何时可以在堆栈上创建临时变量 - 在nowlast之间放置了大量代码,并且寄存器分配算法不能将所有变量放在寄存器中,它将诉诸使用堆栈。因此,对于实际结果,您必须检查生成的机器代码。但坦率地说,除了一个显而易见的事情之外,这里没有太多要优化的地方。如果您关心性能 ,那么您需要担心的是通过PLT进行的大量通话。换句话说 - 请勿使用std::chrono::system_clock::now()

答案 3 :(得分:0)

粗略的是您的代码存在性能问题。

每当在堆栈上调用 operator()时,将会创建并销毁两个变量(实际上它会发生)。

短期内您不会注意到性能,因为系统将始终为Stack保留一些内存,并且每次都要访问相同的内存。

但从长远来看(性能运行),您将能够看到差异。

答案 4 :(得分:0)

我不同意任何其他答案,但让我试着用简单的术语解释这个没有机器代码。我将忽略一些重要的现实生活细节,但不要教导你所提出的概念。

假设您有这些变量的函数

 int a;
 int b;
 int c;
 int d;

在编译期间,编译器会将所有局部变量的大小相加,并且在调用函数时,运行时代码会为所有变量分配足够的堆栈空间。因此,如果sizeof(int)为4,则上述变量需要16个字节的堆栈空间。大多数编译器使用机器寄存器来保存堆栈指针(sp),因此当调用我们的函数时,运行时代码将执行类似

的操作。
sp = sp + 16

为我们的4个变量预留空间。请注意,如果函数具有1或1000个局部变量,则分配局部变量的运行时代码需要相同的时间。每个变量没有成本(除非他们有ctors才能调用)。如果我们有一个C语句,如

d = b;

伪机器代码看起来像

*(sp + 12)= *(sp + 4)

其中12是堆栈上变量d的偏移量,4是b的偏移量。 (偏移量不会这么简单,堆栈上还有其他东西。)

使用

等成员变量定义结构/类时
class X {
 int a;
 int b;
 int c;
 int d;
 void foo() { d = b; }
};

编译器还会将所有变量的大小和每个变量的偏移量相加。 但现在foo()中的代码变为

*(this+12) = *(this + 4)

虽然sp几乎总是保存在机器寄存器中,但“this”指针很可能只在机器寄存器中。现代编译器会查看最常用的变量,并将这些变量存储在寄存器中。由于'this'通常被引用很多(通常是隐式的),因此它通常被分配给寄存器。当'this'在寄存器中时,性能应该相同。