指针与C ++中的可变速度

时间:2011-08-02 22:17:27

标签: c++ performance variables pointers

在求职面试中,我被问到“在C ++中如何更快地访问变量,尽管是普通的变量标识符或通过指针”。我必须说我对这个问题没有很好的技术答案所以我做了一个疯狂的猜测。

我说访问时间可能与普通变量相同/标识符是指向存储值的内存地址的指针,就像指针一样。换句话说,就速度而言,它们都具有相同的性能,并且指针只是不同,因为我们可以指定我们希望它们指向的内存地址。

面试官似乎对我的回答并不是很有信心/满意(虽然他没有说什么,只是继续问别的东西),所以我来问问他们,我的回答是准确的,如果没有为什么(来自理论和技术POV)。

10 个答案:

答案 0 :(得分:40)

当您访问“变量”时,您会查找地址,然后获取值。

记住 - 指针是一个变量。实际上,你:

a)查找地址(指针变量),

b)获取值(存储在该变量中的地址)

......然后......

c)获取指向的地址的值。

所以是的,通过“指针”访问(而不是直接访问)涉及(一点点)额外工作和(稍微)更长时间。

无论是指针变量(C或C ++)还是引用变量(仅限C ++),都会发生同样的事情。

但差别非常小。

答案 1 :(得分:34)

变量不必存在于主存储器中。根据具体情况,编译器可以将其全部或部分存储在寄存器中,访问寄存器比访问RAM要快得多。

答案 2 :(得分:25)

让我们暂时忽略优化,只考虑抽象机器通过(本地)指针引用局部变量与变量的关系。如果我们将局部变量声明为:

int i;
int *p;

当我们引用i的值时,未经优化的代码必须在当前堆栈指针之后获取12(例如)的值,并将其加载到寄存器中,以便我们可以使用它。当我们引用* p时,相同的未经优化的代码必须从当前堆栈指针之后的16位获取p的值,将其加载到寄存器中,然后获取寄存器指向的值并将其加载到另一个寄存器中所以我们可以像以前一样使用它。工作的第一部分是相同的,但指针访问在概念上涉及在我们处理该值之前需要完成的额外步骤。

我认为,这就是面试问题的重点 - 看看你是否理解了两种访问类型之间的根本区别。您认为局部变量访问涉及一种查找,它确实 - 但是指针访问涉及到相同类型的查找以获取指针的值,然后我们才能开始追踪指向它的东西。在简单的,未经优化的术语中,由于额外的步骤,指针访问速度会变慢。

现在通过优化,可能会发生两次非常接近或相同的情况。确实,如果其他最近的代码已经使用p的值来引用另一个值,那么您可能已经在寄存器中找到了p,因此* p via p的查找与通过堆栈查找i的时间相同指针。但是,出于同样的原因,如果您最近使用了i的值,您可能已经在寄存器中找到 it 。虽然* p的值可能同样如此,但如果确定p在平均时间内没有改变,则优化器只能从寄存器重用其值。重新使用i的值没有这样的问题。简而言之,虽然在优化下访问这两个值可能需要相同的时间,但访问局部变量几乎不会更慢(除非在真正的病态情况下),并且可能更快。这使得它成为面试官问题的正确答案。

在存在内存层次结构的情况下,时间上的差异可能会更加明显。局部变量将在堆栈中彼此靠近,这意味着您第一次访问时很可能在主内存和缓存中找到所需的地址(除非它是第一个局部变量)你在这个例程中访问)。指针指向的地址没有这样的保证。除非最近访问过,否则您可能需要等待缓存未命中甚至页面错误才能访问指向的地址,这可能会使其相对于局部变量减慢数量级。不,这不会一直发生 - 但这是一个可能在某些情况下产生影响的潜在因素,而且这也是候选人在回答这样一个问题时可以提出的。

现在其他评论者提出的问题又是怎样的呢:这有多重要?确实,对于单一访问来说,差异在绝对意义上是微不足道的,就像一粒沙子。但是你把足够的沙粒放在一起,你会得到一个海滩。虽然(继续比喻)如果你正在寻找能够在沙滩路上快速奔跑的人,你不希望有人在他或她开始跑步之前痴迷于扫除路上的每一粒沙子,你我想要一个在他或她不必要地穿过膝盖深处的沙丘时会意识到的人。剖析师不会总是在这里拯救你 - 在这些隐喻的术语中,他们更擅长识别你需要跑来跑去的一块大石头,而不是注意到许多小沙粒会让你失望。因此,我希望我的团队成员能够在基本层面上理解这些问题,即使他们很少使用这些知识。不要在寻求微观优化的过程中停止编写清晰的代码,但要注意可能会降低性能的事情,特别是在设计数据结构时,并了解您是否正在为您支付的价格获得良好的价值。这就是为什么我认为这是一个合理的面试问题,以探讨候选人对这些问题的理解。

答案 3 :(得分:22)

什么paulsm4和LaC说+一点asm:

    int y = 0;
mov         dword ptr [y],0  
    y = x;
mov         eax,dword ptr [x]   ; Fetch x to register
mov         dword ptr [y],eax   ; Store it to y
    y = *px;
mov         eax,dword ptr [px]  ; Fetch address of x 
mov         ecx,dword ptr [eax] ; Fetch x 
mov         dword ptr [y],ecx   ; Store it to y

另一方面,它并不重要,也可能更难以优化(例如,你不能保持cpu寄存器中的值,因为指针只指向内存中的某个位置)。所以y = x的优化代码;可能看起来像这样:

mov dword ptr [y], ebx - 如果我们假设本地var x存储在ebx

答案 4 :(得分:3)

我认为面试官正在寻找你提到注册这个词。同样,如果您将变量声明为寄存器变量,编译器将尽最大努力确保它存储在CPU的寄存器中。

关于总线访问和其他类型的变量和指针的协商的一些聊天将有助于构建它。

答案 5 :(得分:2)

变量保存某种类型的值,访问变量意味着从内存或寄存器中获取此值。从内存中获取值时,我们需要从某个地方获取它的地址 - 大多数时候它必须加载到寄存器中(有时它可能是load命令本身的一部分,但这种情况非常罕见)。

指针保存值的地址;该值必须在内存中,指针本身可以在内存中或寄存器中。

我希望通过指针进行平均访问比通过变量访问值要慢。

答案 6 :(得分:2)

您的分析忽略了指针本身是必须也可以访问的内存变量的常见场景。

有许多因素会影响软件的性能,但是如果你对所涉及的变量做出某些简化的假设(特别是它们没有以任何方式缓存),那么每个级别的指针间接都需要额外的内存访问。 / p>

int a = 1234; // luggage combination
int *b = &a;
int **c = &b;
...
int e = a; // one memory access
int e = *b; // two memory accesses
int e = **c; // three memory accesses

所以“更快”的简短回答是:忽略可能发生的编译器和处理器优化,直接访问变量的速度更快。

在最佳情况下,此代码在紧密循环中重复执行,指针值可能会缓存到CPU寄存器中,或者最坏情况下缓存到处理器的L1缓存中。在这种情况下,第一级指针间接可能比直接访问变量快或快,因为“直接”可能意味着通过“堆栈指针”寄存器(加上一些偏移)。在这两种情况下,您都使用CPU寄存器作为指向值的指针。

还有其他可能影响此分析的方案,例如全局或静态数据,其中变量的地址被硬编码到指令流中。在这种情况下,答案可能取决于所涉及的处理器的具体情况。

答案 7 :(得分:2)

我认为问题的关键部分是“访问变量”。对我来说,如果变量在范围内,为什么要创建一个指向它的指针(或引用)来访问它?使用指针或引用只有在变量本身是某种数据结构或者以某种非标准方式访问它时(例如将int解释为float)才有意义。

只有在非常特殊的情况下,使用指针或引用才会更快。在一般情况下,在我看来,就优化而言,你会试图再次猜测编译器,我的经验告诉我,除非你知道你在做什么,否则这是一个坏主意。

它甚至取决于关键字。 const关键字可能意味着变量在编译时完全优化。这比指针快。寄存器关键字does not guarantee,该变量存储在寄存器中。那你怎么知道它是否更快?我认为答案是因为没有一种尺寸适合所有答案。

答案 8 :(得分:2)

paulsm4和LaC已经与其他成员一起很好地解释了它。我想强调当指针指向已被分页的堆中的某些内容时的分页效果。

=>局部变量在堆栈或寄存器中可用
=>在指针的情况下,指针可能指向一个不在缓存中的地址,分页肯定会降低速度。

答案 9 :(得分:1)

我认为更好的答案可能取决于指针指向的位置。注意,变量可能已经在缓存中。但是,指针可能会导致获取惩罚。它类似于链表与矢量性能权衡。 Vector是缓存友好的,因为你的所有记忆都很有意义。但是,由于链接列表包含指针,因此可能会导致缓存损失,因为内存可能遍布整个地方