指针与C ++中的variables速度
在一次求职面试中,我被问到“在C ++中,如何更快速地访问variables,尽pipe是普通的variables标识符或者指针”。 我必须说我对这个问题没有一个很好的技术答案,所以我大肆猜测。
我说访问时间可能会和普通的variables/标识符一样,是一个指向存储值的地址的指针,就像指针一样。 换句话说,就速度而言,它们都具有相同的性能,并且指针只是不同的,因为我们可以指定我们希望它们指向的内存地址。
面试官似乎对我的回答(虽然他没有说什么,只是继续询问别的事情)似乎不是很确信/满意,所以我要来问问我的答案是否准确,如果不是,理论和技术POV)。
variables不必居住在主内存中。 根据具体情况,编译器可以将其全部或部分存储在寄存器中,访问寄存器比访问RAM要快得多。
当你访问一个“variables”时,你查找地址,然后获取值。
记住 – 一个指针是一个variables。 所以其实你呢
a)查找(指针variables的)地址,
b)获取值(存储在该variables中的地址)
… 接着 …
c)获取指向地址的值。
所以是的,通过“指针”(而不是直接)访问是否涉及(稍微)额外的工作和(稍微)更长的时间。
无论是否是指针variables(C或C ++)还是引用variables(仅限C ++),都会发生完全相同的事情。
但差别非常小。
让我们暂时忽略优化,只要想一下抽象机器通过(本地)指针引用局部variables和variables需要做什么。 如果我们有局部variables声明为:
int i; int *p;
当我们引用i的值时,未优化的代码必须得到当前栈指针12位(比如说)的值并将其加载到寄存器中,以便我们可以使用它。 而当我们引用* p时,相同的未经优化的代码必须从当前堆栈指针的后16位获取p的值,将其加载到寄存器中,然后获取寄存器指向的值并将其加载到另一个寄存器所以我们可以像以前一样使用它。 第一部分工作是一样的,但是指针访问在概念上涉及额外的步骤,需要完成之后才能处理这个值。
那就是我认为面试问题的重点 – 看看你是否理解这两种访问types之间的根本区别。 你以为局部variables访问涉及到一种查找,而且它是这样做的 – 但是指针访问涉及到非常相同types的查找,以便在我们可以开始去指向它的东西之前得到指针的值。 在简单的未优化术语中,由于额外的步骤,指针访问将会变得更慢。
现在进行优化,可能会发生两次非常接近或相同的情况。 确实,如果其他最近的代码已经使用p的值来引用另一个值,那么您可能已经在寄存器中find了p,所以通过p的* p查找与通过堆栈查找i的时间相同指针。 同样的道理,如果你最近使用了i的值,你可能已经在寄存器中find它了。 尽pipe* p的值可能是相同的,但是如果确定p在平均时间内没有改变,优化器只能从寄存器中重新使用它的值。 它没有这个问题重用我的价值。 简而言之,访问这两个值在优化时可能需要相同的时间,访问局部variables几乎不会变慢(除非在病态情况下),并且可能会更快。 这就是面试官问题的正确答案。
在存在层次结构的情况下,时间差别可能变得更加明显。 局部variables将在堆栈中彼此靠近,这意味着在第一次访问它的时候,很可能已经在主内存和caching中find了所需的地址(除非它是第一个局部variables你在这个例程中访问)。 指针指向的地址没有这样的保证。 除非最近访问过,否则可能需要等待caching未命中,甚至是页面错误,才能访问指向的地址,这可能会使得它比本地variables慢几个数量级。 不,这种情况不会一直发生 – 但这是一个潜在的因素,可能会在某些情况下有所作为,候选人也可以提出这样的问题。
那么其他评论者提出的问题呢,有多重要呢? 对于一次访问来说,确实如此,从绝对的angular度来看,这种差异将会很小,就像一粒沙子。 但是,你把足够的沙子放在一起,你得到一个沙滩。 尽pipe(继续比喻),如果你正在寻找一个可以在沙滩上快速奔跑的人,你不希望有人会在他或她可以开始跑步之前,当他或她不必要地穿过膝盖深的沙丘时,会希望有人知道。 探险家并不总是会在这里帮助你 – 用这些比喻的话来说,他们在认识一个你需要四处奔走的大石头时,要比注意到许多让你沉迷的沙粒less得多。 所以我希望我的团队中的人员能够从根本上理解这些问题,即使他们很less使用这些知识。 不要停止在寻求微观优化的时候写出明确的代码,但要注意那些可能会降低性能的东西,特别是在devise数据结构的时候,以及是否对你付出的代价有很好的价值。 这就是为什么我认为这是一个合理的面试问题,以探讨候选人对这些问题的理解。
什么paulsm4和LaC说+有点asm:
int y = 0; mov dword ptr [y],0 y = x; mov eax,dword ptr [x]; 取x注册 mov dword ptr [y],eax; 存储到y y = * px; mov eax,dword ptr [px]; 取x的地址 mov ecx,dword ptr [eax]; 抓取x mov dword ptr [y],ecx; 存储到y
另一方面,这不重要,这也许是难以优化(fe。你不能保持在cpu寄存器的价值,因为指针只是指向内存中的某个地方)。 所以优化的代码为y = x; 可能看起来像这样:
mov dword ptr [y], ebx
– 如果我们假设本地var x被存储在ebx
我认为面试官正在找你提到注册这个词。 如同你所说的那样,如果你将一个variables声明为一个寄存器variables,编译器将尽最大努力确保它被存储在CPU的一个寄存器中。
总线访问和其他types的variables和指针的谈判有点帮助框架。
variables保存某种types的值,访问variables意味着从内存或寄存器获取该值。 从内存中获取值时,我们需要从某个地方得到它的地址 – 大多数情况下它必须加载到一个寄存器中(有时它可以是加载命令本身的一部分,但这是非常罕见的)。
指针保存一个值的地址; 这个值必须在内存中,指针本身可以在内存中或寄存器中。
我希望平均来说,通过指针访问将比通过variables访问值慢。
我认为问题的关键部分是“访问一个variables”。 对我来说,如果一个variables在范围内,为什么你会创build一个指向它(或引用)的指针来访问它? 使用指针或引用只有在variables本身就是某种数据结构,或者以非标准方式访问(比如将int解释为浮点数)时才有意义。
只有在非常特殊的情况下,使用指针或引用才会更快。 在一般情况下,在我看来,就优化而言,你会试图再次猜测编译器,而且我的经验告诉我,除非你知道自己在做什么,否则这是一个坏主意。
它甚至会取决于关键字。 const关键字可能意味着该variables在编译时被完全优化。 这比指针快。 register关键字不保证variables存储在寄存器中。 那么你怎么知道它是否更快? 我认为答案是,这取决于没有一个适合所有的答案。
我认为更好的答案可能取决于指针指向的位置。 请注意,variables可能已经在caching中。 但是,指针可能会导致取消处罚。 这与链接列表和vector性能折衷类似。 Vector是caching友好的,因为你所有的内存都是连续的。 然而,由于链表包含指针,因此链表可能会受到caching惩罚,因为内存可能遍布整个地方
你的分析忽略了指针本身是一个内存variables的常见情况,这个variables也必须被访问。
影响软件性能的因素很多,但是如果您对所涉及的variables进行某些简化的假设(特别是它们没有以任何方式被caching),那么每个指针间接级别都需要额外的内存访问。
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
因此,“哪个更快”的简短答案是:忽略可能正在发生的编译器和处理器优化,直接访问variables会更快。
在最好的情况下,这个代码在一个紧密的循环中重复执行,指针值可能被caching到CPU寄存器中,或者在最坏的情况下被caching到处理器的L1caching中。 在这种情况下,由于“直接”可能意味着通过“堆栈指针”寄存器(加上一些偏移量),第一级指针间接可能比直接访问variables快或者更快。 在这两种情况下,您都使用一个CPU寄存器作为指针。
还有其他一些可能影响分析的场景,例如全局或静态数据,其中variables的地址被硬编码到指令stream中。 在这种情况下,答案可能取决于所涉及的处理器的细节。
paulsm4和LaC已经和其他成员一起很好地解释了它。 当指针指向已被分页的堆中的某些内容时,我想强调分页的效果。
=>局部variables可以在堆栈或寄存器中使用
=>在指针的情况下,指针可能指向一个不在caching中的地址,寻呼肯定会减慢速度。