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

Jer*_*rry 44 c++ variables performance pointers

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

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

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

pau*_*sm4 42

当您访问"变量"时,您查找地址,然后获取值.

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

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

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

... 然后 ...

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

所以是的,通过"指针"(而不是直接)访问DOES涉及(一点点)额外的工作和(略微)更长的时间.

无论它是指针变量(C或C++)还是引用变量(仅限C++),都会发生完全相同的事情.

但差异非常小.

  • 除非它很大,例如在装载撞击存储危险期间. (9认同)
  • 我不明白为什么你说差别很小.如果它是内存访问量的两倍,则可能需要两倍的时间. (5认同)
  • 它可以是两倍多 - 请参阅下面关于内存层次结构效果的答案.而且,你是对的,单个变量访问的差异通常是微不足道的,但是把足够的沙子放在一起,你可以得到一个海滩.例如,如果您创建一个频繁访问的数据结构,需要几个额外的间接级别,因为您不理解该问题,突然之间差异可能看起来不那么小.关键是"候选人是否了解潜在问题",而不是"在这种特殊情况下我们应该做些什么". (3认同)
  • 我不认为我们可以假设"变量"具有可直接访问的内存地址,尤其是非局部变量. (2认同)
  • 指针并不总是变量.例如,`&var`和`this`是不是变量的指针. (2认同)

LaC*_*LaC 35

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

  • @littleadv,重读我的回答:这是一个例子,不是假设.问题是Jerry认为*标识符是指向存储值的内存地址的指针*,而实际上并非如此.实际上,可能根本没有内存地址. (11认同)
  • @littleadv:不同意,寄存器赋值的速度优点对于常规(pre c ++ 0x`auto` allocation)局部变量的目的而言是*central*. (4认同)
  • 分配变量实际上并不是一种优化,它是编译的必要部分.如果在函数中定义"int a",则不是说"RAM中有一个名为'a'的位置",然后编译器通过将其放入寄存器来优化它.你所说的是"有一个名为"的整数变量,编译器必须决定把它放到哪里. (4认同)
  • 如果你想声称变量"更快",那么提到编译器优化并不是正确的理由. (3认同)
  • 根据我的信息,指针永远不会存储在寄存器中,只有当一个变量被大量使用时才会被保存,如果一个指针要保存在寄存器中,那么它的值不是指向的变量的值,我正在学习编译器课程,这正是我正在做的事情. (2认同)

dew*_*ell 27

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

int i;
int *p;
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

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


Mar*_*uła 23

什么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

  • +1表示操作码.我们需要更多的程序员来理解如何阅读程序集. (3认同)