性能:C++中的指针解引用

4

每次我在类中通过指针访问并循环数组时,都会问自己同一个问题:

每次迭代时,解引用指针是否会产生额外的开销?解引用链是否会增加开销? 例如:

ClassA *a = new ClassA();
ClassB *b = new ClassB();

for( int i = 0; i < 10; i++){

   a->b->array[i].foo();

}

如果我要猜的话,这可能涉及20个取消引用步骤,每个指针一个,10次迭代。但我同样可以想象,由于编译器将链接的指针转换为单个指针,因此它被减少到了10次。我甚至可以想象,由于某些缓存技巧或其他原因,它被减少到了1次。
有人能告诉我,并可能向我解释一下,这在性能方面的表现如何?非常感谢!
顺便说一句,我知道类似的问题已经在这里得到了回答,但是我无法推断出这个特定主题的答案。所以请不要责怪我再次提出这个话题。

1
b是A的成员吗?全局范围内的ClassB实例b与作为A成员的未知对象b有什么关系呢? - franji1
如果我要猜的话,我会说这涉及到20个解引用步骤 - 实际上是30个。 a->b->array[i]都是指针解引用,因此每次循环迭代您有3个解引用,共10次迭代。 - Remy Lebeau
3个回答

7

编译器(尤其是优化器)如何生成代码取决于它本身,根据as-if规则,只要用户在外部无法区分程序的行为差异,编译器可以随意处理,现代编译器能够非常巧妙地应用优化。

实际上,我认为大多数现代优化器只有在无法确定foo()内部发生了什么时才无法优化循环 - 特别是,如果它们无法保证foo()的实现不会更改ab的值,则它们将被迫为每个循环迭代执行单独的ab解引用,以确保即使ab的值发生变化也能得到正确的结果。

您可以自己找出如果您不介意阅读一些汇编代码会发生什么 - 只需启用优化编译程序(例如g++ -O3 -S mytest.cpp)并阅读生成的mytest.S文件以查看编译器的操作。尝试在同一文件中实现foo()(这样编译器肯定能看到foo()的内容)和在不同文件中实现foo()(这样编译器可能需要将foo()视为“黑盒”)并查看它们之间的差异。


1
非常感谢,我甚至没有考虑到foo()可能会改变其中一个指针的事实,但这实际上是一个非常有趣的事情要考虑,当我们猜测编译器会做什么时。我必须承认,在此之前我从未读过一行汇编代码,但我想如果我对优化感兴趣,现在是开始的好时机 :D - user3808217

3

通过这样做,您可以确保摆脱一些取消引用:

// create a pointer to the b class outside of the loop
ClassB * bptr = a->b;        

// use the pointer inside the loop
for( int i = 0; i < 10; i++){

    bptr->array[i].foo();

}

3
在进入循环之前,通过保存对array的指针并使循环在每次迭代时增加该指针来进一步优化。这样可以将30个解引用操作减少到10个,而不是像您的示例中那样减少到20个。 - Remy Lebeau

1
我期望只进行1次内存访问,因为在循环内a和a->b的值不会改变,所以没有必要再次获取它们。同时,对于a->b->array[i],i的所有值都是已知的,因此可以进行预取。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接