1

假设代码打击是我的班级。它是简化的而不是完整的。让我们专注于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但我也听说编译时局部变量甚至可能不存在。所以不知何故,开销也不存在。

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

4

5 回答 5

2

在 x86-64 上,我希望这段代码最终会同时出现nowdelta在 RAX 中分配。在汇编语言中,代码将按以下顺序显示:

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:EAX 中返回 64 位值,在这种情况下,事情最终会与上面的非常相似,只需使用一个寄存器。

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

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

于 2013-01-19T03:47:23.993 回答
1

它不会产生太大的影响(如果有的话)。操作系统根据页面分配内存(包括堆栈)。因此堆栈可能不会完成一个页面,因此该过程不需要上下文切换来获得另一个页面。

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

此外,像您这样的一些人似乎专注于微观性能改进,但避开了大局。最好先找出瓶颈在哪里,然后集中精力解决这些问题。记住80/20法则。

于 2013-01-19T03:18:11.850 回答
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.

根据上下文,它可能会得到进一步优化。或者它可能会变得更糟。举个例子,什么时候可以在堆栈上创建一个临时变量——你在 ofnow和之间放置了很多代码,last并且寄存器分配算法不能将所有变量都放在寄存器中,它会求助于使用堆栈。因此,对于实际结果,您必须检查生成的机器代码。但坦率地说,除了一件显而易见的事情外,这里没有太多需要优化的地方。如果您非常关心性能那么您必须担心的是通过 PLT 进行的大量调用。换句话说——不要使用std::chrono::system_clock::now().

于 2013-01-19T03:38:05.170 回答
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”在寄存器中时,性能应该是相同的。

于 2013-01-19T04:39:55.957 回答
0

粗略地说,您的代码中存在性能问题。

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

短期内你不会注意到性能,因为系统总是会为 Stack 保留一些内存,并且每次访问相同的内存。

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

于 2013-01-19T03:04:55.063 回答