我使用这个简单的示例来说明一个问题,在此问题中我试图优化堆栈使用率。假设我有一个这样的结构:
// Something.h
struct Something {
int val;
bool operator==(const Something& rhs);
bool operator!=(const Something& rhs);
};
// Something.cpp
bool Something::operator==(const Something& rhs) {
return val == rhs.val;
}
bool Something::operator!=(const Something& rhs) {
return !(*this == rhs);
}
调用operator!=()会将两个堆栈帧压入堆栈(一个用于!=,另一个用于==)。我应该内联运算符!=(),以便==和!=都使用相同数量的堆栈吗?
您应该使用链接时间优化(LTO),以便它们中的任何一个都可以完全内联到调用站点中,尤其是在这样的情况下。
但是,如果您不想使用LTO进行跨文件内联,那么可以的话,最好将operator !=
return !(*this == rhs);
定义放入)中的类定义中,.h
这样每个调用者都可以看到它,并且可以将其内联到刚刚包含的文件中的.h
。然后,调用者的asm将调用相同的operator==
定义,但以相反的方式使用结果。例如test al,al
/,jnz
而不是jz
在结果上进行分支。
如果您不使用LTO并且在编译时内联中不显示该定义,那么最好的做法是编译器在编译该内联时将其内联operator==
到operator!=
独立定义中.cpp
。然后,您在机器代码中就有两个大小相似的函数,它们之间的区别只是一个布尔值求反。这些函数的用户(从其他文件)将调用另一个函数,因此它们都占用了I缓存/代码占用空间。
// Something.h
struct Something {
int val;
bool operator==(const Something& rhs);
bool operator!=(const Something& rhs) { return !(*this == rhs); }
};
// simulated #include for one-file demo purposes
// Some other .cpp file, operator== definition not visible.
int foo(Something &a, Something &b)
{
if (a != b) {
return a.val;
} else {
return b.val;
}
}
用于x86-64(Godbolt)的GCC -O3编译如下:
foo(Something&, Something&):
push rbp
mov rbp, rsi
push rbx
mov rbx, rdi # save the pointers in call-preserved regs
sub rsp, 8
call Something::operator==(Something const&)
test al, al # set FLAGS from the bool retval
cmovne rbx, rbp # select the right pointer
mov eax, DWORD PTR [rbx] # and load from it
add rsp, 8 # epilogue
pop rbx
pop rbp
ret
请注意,此代码调用Something::operator==
在编译时无法内联(它可以在与LTO链接时进行内联)。它只是使用cmovne
而不是cmove
是否调用了一个单独的operator!=
。
该operator!=
联字面上零额外费用,并且或者函数的所有呼叫使用相同的独立定义,既可以节省代码的足迹。对性能有好处,尤其是当您具有同时使用两个运算符的代码以使其在缓存中保持最新状态时。
当然,operator==
如果仅是class,则内联也可以节省大量资金int
;根本没有呼叫通常会好很多,因为不需要在某些内容周围保留寄存器。
(当然,在这种情况下,我的例子是太微不足道了:如果他们是平等的,那么它仍然可以恢复a.val
,因为它知道这是一样的b.val
所以,如果你取消注释。operator==
在Godbolt链接定义,foo
编译为mov eax, DWORD PTR [rdi]
/ ret
,甚至从来没有接触b
。)
本文收集自互联网,转载请注明来源。
如有侵权,请联系[email protected] 删除。
我来说两句