这是一个真实的示例:在旧编译器上不动点相乘。
这些不仅在没有浮点的设备上很方便,而且在精度方面也很出色,因为它们为您提供32位精度并带有可预测的错误(浮点只有23位,更难预测精度损失)。也就是说,在整个范围内具有统一的绝对精度,而不是相对接近的相对精度精度(float
)。
现代编译器很好地优化了此定点示例,因此,对于仍需要特定于编译器代码的更现代示例,请参见
C没有全乘法运算符(来自N位输入的2N位结果)。用C表示它的通常方法是将输入转换为更宽的类型,并希望编译器认识到输入的高位并不有趣:
// on a 32-bit machine, int can hold 32-bit fixed-point integers.
int inline FixedPointMul (int a, int b)
{
long long a_long = a; // cast to 64 bit.
long long product = a_long * b; // perform multiplication
return (int) (product >> 16); // shift by the fixed point bias
}
这段代码的问题是我们做了一些不能直接用C语言表达的事情。我们想要将两个32位数字相乘并得到64位结果,然后返回中间的32位。但是,在C中不存在此乘法。您所要做的就是将整数提升为64位,并执行64 * 64 = 64乘法。
但是,x86(以及ARM,MIPS等)可以在一条指令中进行乘法运算。一些编译器过去常常忽略这一事实,并生成调用运行时库函数进行乘法的代码。通常由库例程完成16的移位(x86也可以进行此类移位)。
因此,我们只剩下一个或两个库调用即可进行乘法运算。这具有严重的后果。不仅转移速度变慢,而且必须在函数调用之间保留寄存器,并且这也不利于内联和代码展开。
如果在(内联)汇编器中重写相同的代码,则可以显着提高速度。
除此之外:使用ASM不是解决问题的最佳方法。如果无法用C表示大多数编译器,则允许您以固有形式使用一些汇编程序指令。例如,VS.NET2008编译器将32 * 32 = 64位mul公开为__emul,将64位移位公开为__ll_rshift。
使用内在函数,您可以以C编译器有机会了解发生了什么的方式来重写函数。这样就可以内联代码,分配寄存器,消除公共子表达式并实现常数传播。这样,您将获得比手写汇编代码更大的性能提升。
供参考:VS.NET编译器的定点mul的最终结果是:
int inline FixedPointMul (int a, int b)
{
return (int) __ll_rshift(__emul(a,b),16);
}
定点分割的性能差异更大。通过编写几行asm行,我对除法重固定点代码进行了高达10的改进。
使用Visual C ++ 2013会为两种方式提供相同的汇编代码。
2007年的gcc4.1还很好地优化了纯C版本。(Godbolt编译器资源管理器没有安装任何较早的gcc版本,但大概是较旧的GCC版本也可以在没有内部函数的情况下执行此操作。)
请参阅Godbolt编译器资源管理器上的 x86(32位)和ARM的source + asm 。(不幸的是,它没有足够老的编译器可以从简单的纯C版本生成错误的代码。)
现代的CPU可以做的事情C没有运营商都一样,popcnt
还是位扫描,找到第一个或最后一组位。(POSIX具有ffs()
功能,但其语义与x86 bsf
/ 不匹配bsr
。请参见https://en.wikipedia.org/wiki/Find_first_set)。
有些编译器有时可以识别出一个循环,该循环可以计算整数中设置位的数量并将其编译为一条popcnt
指令(如果在编译时启用),但是__builtin_popcnt
在GNU C或x86(如果您只使用x86)上使用则更加可靠使用SSE4.2定位硬件:_mm_popcnt_u32
从<immintrin.h>
。
或者在C ++中,分配给a std::bitset<32>
并使用.count()
。(在这种情况下,该语言已经找到了一种通过标准库可移植地公开popcount优化实现的方法,该方法始终可以编译为正确的东西,并且可以利用目标支持的任何优势。)另请参见https ://en.wikipedia.org/wiki/Hamming_weight#Language_support。
类似地,ntohl
可以bswap
在具有此功能的某些C实现中编译为(x86 32位字节交换以进行字节序转换)。
内在函数或手写汇编的另一个主要领域是使用SIMD指令进行手动向量化。编译器对于像这样的简单循环来说还不错dst[i] += src[i] * 10.0;
,但是当事情变得更加复杂时,它们通常做得不好或者根本不会自动向量化。例如,您不太可能获得诸如如何使用SIMD实现atoi的信息?由编译器从标量代码自动生成。