取消引用指针有多昂贵?
对指针执行取消引用操作有多昂贵?
我可以想象,内存传输在某种程度上与对象大小成正比,但我想知道取消引用操作部分是多么昂贵。
当解释为机器码时,解引用可能意味着不同的事情,取决于你对解除引用的对象所做的事情。 通过指针访问类的单个成员通常是便宜的。 例如,如果c是一个指向class C
的实例的int
成员n,那么就像这样:
int n = c->n;
可能翻译成一个或两个机器指令,并可能加载一个单一的内存访问寄存器。
另一方面,这意味着完成c:所指向的对象的拷贝。
C d = *c;
这个成本取决于C的大小,但是请注意,它是主要开销的副本,而“取消引用”部分实际上只是在复制指令中“使用”指针地址。
请注意,访问大对象的成员通常需要指针偏移计算和内存访问,而不pipe对象是否是本地对象。 通常只有非常小的对象才被优化,只能在寄存器中生存。
如果你关心指针的引用成本,那么不要这样做。 这两者之间的差异是语言语义的差异,并且在生成机器码的时候指针和参考访问看起来完全一样。
这取决于你对解除引用的指针做了什么。 单纯的解除引用操作本身并不起作用。 它只是得到一个代表你的对象的typesT
的左值,如果你的指针是一个T*
struct a { int big[42]; }; void f(a * t) { // does nothing. Only interesting for standard or compiler writers. // it just binds the lvalue to a reference t1. a & t1 = *t; }
如果实际上从取消引用操作返回的左值表示的对象中获取值,编译器必须复制该对象包含的数据。 对于一个简单的POD,这仅仅是一个memcpy
:
a aGlobalA; void f(a * t) { // gets the value of of the object denoted by *t, copying it into aGlobalA aGlobalA = *t; }
我的gcc端口输出这个代码为f:
sub $29, $29, 24 ; subtract stack-pointer, creating this frame stw $31, $29, 20 ; save return address add $5, $0, $4 ; copy pointer t into $5 (src) add $4, $0, aGlobalA ; load address of aGlobalA into $4 (dst) add $6, $0, 168 ; put size (168 bytes) as 3rd argument jal memcpy ; call memcpy ldw $31, $29, 20 ; restore return address add $29, $29, 24 ; add stack-pointer, destroying this frame jr $31
经过优化的机器代码将使用内联代码,而不是调用memcpy
,但这只是一个实现细节。 重要的是,只是*t
不执行任何代码,但访问该对象的值实际上需要复制它。
我们需要做一个具有用户定义的复制赋值操作符的types,事务更复杂:
struct a { int big[42]; void operator=(a const&) { } };
现在,相同函数f
的代码如下所示:
sub $29, $29, 8 add $29, $29, 8 jr $31
哈。 但是这并不奇怪,不是吗? 毕竟,编译器应该调用我们的operator=
,如果它什么都不做,整个函数也什么都不做!
结论
我认为我们可以得出的结论是,这一切都取决于如何使用operator*
的返回值。 如果我们只有一个我们废除的指针,我们可以看到上面的代码生成在很大程度上取决于情况。 我没有表明,如果我们取消引用重载operator*
的类types,它的行为如何。 但本质上,它只是像我们用operator=
看到的那样。 所有的测量都是用-O2
完成的,所以编译器正确地内联了:)
在普通系统上取消引用指针最重要的因素是你可能会产生caching未命中。 SDRAM存储器中的随机访问花费几十纳秒(例如64)。 在gigaherz处理器上,这意味着你的处理器正在闲置数百(或者>千)个周期,而不能在此期间做其他任何事情。
只有基于SRAM的系统(您只能在embedded式软件中find),或者当您的软件进行caching优化时,其他post中讨论的因素才会起作用。
取消引用的代价可能很高,主要是因为它会花费一个从内存中获取数据的指令,这些数据可能很远,而且不会显示引用的局部性。 在这种情况下,处理器应该从非高速缓冲存储器,甚至是硬盘中读取数据(在发生硬页面错误的情况下)。
解引用(多个)成本CPU周期。
而不是写作:
string name = first->next->next->next->name; int age = first->next->next->next->age; this is O(n)
把它写成:
node* billy_block = first->next->next->next; string name = billy_block->name; int age = billy_block->age; this is O(1)
所以你的代码将不会“询问”每一个块只是为了到达第四个块。
多个解引用就像有一个邻居只知道旁边的邻居。
想象一下,如果你问第一个街区的人,你的朋友Billy居住在哪里,他会告诉你他不认识你的朋友,他会告诉你,他只知道旁边的邻居,然后他会告诉你问他的邻居,然后你问邻居,他会回答第一个街区相同的东西,你不停地问,直到你到达你朋友的街区。 效率不高
指针的解除引用应该不会超过将地址复制到(地址)寄存器。 就这样。