我希望下面的
multiply_by_pow_of_2
函数有一个更优化的乘法代码,因为我使用了[[ assume( std::has_single_bit( multiplier ) ) ]];
,它应该告诉编译器第二个参数multiplier
始终是2的幂。所以也许它可以生成更快代码。然而它生成相同的代码(使用imul
):
multiply_by_pow_of_2(unsigned int, unsigned int):
mov eax, esi
imul eax, edi
ret
话虽如此,在 main 中调用此函数并将其生成的代码与
argc * 8
的代码进行比较表明,两者都更改为通常的 lea
(而不是类似 左移):
lea esi, [0+rdi*8]
这里(直播):
#include <bit>
#include <cstdio>
unsigned multiply_by_pow_of_2( unsigned num, unsigned multiplier ) noexcept
{
[[ assume( std::has_single_bit( multiplier ) ) ]];
return num * multiplier;
}
int main( int argc, char* argv[ ] )
{
unsigned result;
if constexpr ( true )
{
result = multiply_by_pow_of_2( argc, 8 );
}
else
{
result = argc * 8;
}
std::printf( "%d\n", result );
}
给出(使用
-O3
):
multiply_by_pow_of_2(unsigned int, unsigned int):
mov eax, esi
imul eax, edi
ret
.LC0:
.string "%d\n"
main:
sub rsp, 8
lea esi, [0+rdi*8] ; <- here
xor eax, eax
mov edi, OFFSET FLAT:.LC0
call printf
xor eax, eax
add rsp, 8
ret
无论有没有假设语句,函数的代码都保持不变。我是否无法让编译器相信
multiplier
是 2 的幂?这怎么办?
首先,乘法指令在当前的 CPU 上速度非常快,因此使用移位而不是乘法不一定会取得很大的胜利。
其次,要用移位代替乘法,仅知道操作数有一个位是不够的 - 您需要知道该位实际放置在哪个位位置(例如 b),以便您可以执行左移移动 b 个位置。如果编译器无法推断出该信息,则需要例如发出计数尾随零指令来查找 b,然后进行移位。这几乎肯定比单纯的乘法更昂贵。
另一方面,如果输入是常量,则编译器可以在编译时计算 b 并发出您所见证的无乘法代码。