我如何告诉MSVC编译器使用64位/ 32位除法运算来为x86-64目标计算以下函数的结果:
#include <stdint.h>
uint32_t ScaledDiv(uint32_t a, uint32_t b)
{
if (a > b)
return ((uint64_t)b<<32) / a; //Yes, this must be casted because the result of b<<32 is undefined
else
return uint32_t(-1);
}
我希望代码在if
语句为true时编译为使用64位/ 32位除法运算,例如:
; Assume arguments on entry are: Dividend in EDX, Divisor in ECX
mov edx, edx ;A dummy instruction to indicate that the dividend is already where it is supposed to be
xor eax,eax
div ecx ; EAX = EDX:EAX / ECX
...但是x64 MSVC编译器坚持使用128bit / 64bitdiv
指令,例如:
mov eax, edx
xor edx, edx
shl rax, 32 ; Scale up the dividend
mov ecx, ecx
div rcx ;RAX = RDX:RAX / RCX
参见:https : //www.godbolt.org/z/VBK4R7 1
根据对这个问题的回答,128bit / 64bitdiv
指令并不比64bit / 32bitdiv
指令快。
这是一个问题,因为它不必要地减慢了我的DSP算法的速度,从而使成千上万的这种比例划分成为可能。
我通过修补可执行文件以使用64位/ 32位div指令来测试此优化:根据指令产生的两个时间戳,性能提高了28%rdtsc
。
(编者注:大概是在某些最新的Intel CPU上。AMDCPU不需要这种微优化,如链接的问答中所述。)
当前的编译器(gcc / clang / ICC / MSVC)都不会从可移植的ISO C源代码进行此优化,即使您让他们证明这样b < a
做也是如此,商数才适合32位。(例如,if(b>=a) __builtin_unreachable();
在Godbolt上使用GNU C )。这是一个错过的优化;在此问题解决之前,您必须使用内部函数或内联汇编来解决它。
(或者改为使用GPU或SIMD;如果许多元素具有相同的除数,请参阅https://libdivide.com/,以使SIMD一次计算一个乘法逆,然后重复应用。)
_udiv64
从Visual Studio 2019 RTM开始可用。
在C模式(-TC
)中,显然总是已定义。在C ++模式下,#include <immintrin.h>
根据Microsoft文档,您需要。或intrin.h
。
https://godbolt.org/z/vVZ25L(或在Godbolt.ms上,因为最近Godbolt主站点上的MSVC无法正常工作1。)
#include <stdint.h>
#include <immintrin.h> // defines the prototype
// pre-condition: a > b else 64/32-bit division overflows
uint32_t ScaledDiv(uint32_t a, uint32_t b)
{
uint32_t remainder;
uint64_t d = ((uint64_t) b) << 32;
return _udiv64(d, a, &remainder);
}
int main() {
uint32_t c = ScaledDiv(5, 4);
return c;
}
_udiv64将产生64/32 div。左右两个移位是错过的优化。
;; MSVC 19.20 -O2 -TC
a$ = 8
b$ = 16
ScaledDiv PROC ; COMDAT
mov edx, edx
shl rdx, 32 ; 00000020H
mov rax, rdx
shr rdx, 32 ; 00000020H
div ecx
ret 0
ScaledDiv ENDP
main PROC ; COMDAT
xor eax, eax
mov edx, 4
mov ecx, 5
div ecx
ret 0
main ENDP
因此,我们可以看到_udiv64
,即使在这种情况下MSVC也不会溢出,并且可能已经编译main
为mov eax, 0ccccccccH
/ ,所以MSVC不会进行常量传播ret
。
更新#2 https://godbolt.org/z/n3Dyp-添加了使用Intel C ++编译器的解决方案,但这效率较低,并且由于它是嵌入式asm,因此将无法实现常数传播。
#include <stdio.h>
#include <stdint.h>
__declspec(regcall, naked) uint32_t ScaledDiv(uint32_t a, uint32_t b)
{
__asm mov edx, eax
__asm xor eax, eax
__asm div ecx
__asm ret
// implicit return of EAX is supported by MSVC, and hopefully ICC
// even when inlining + optimizing
}
int main()
{
uint32_t a = 3 , b = 4, c = ScaledDiv(a, b);
printf( "(%u << 32) / %u = %u\n", a, b, c);
uint32_t d = ((uint64_t)a << 32) / b;
printf( "(%u << 32) / %u = %u\n", a, b, d);
return c != d;
}
脚注1:Matt Godbolt的主站点的非WINE MSVC编译器暂时消失了。微软运行https://www.godbolt.ms/,将最新的MSVC编译器托管在真实的Windows上,并且通常将Godbolt.org主站点转发给MSVC。)
似乎godbolt.ms会生成短链接,但不会再次扩展它们!无论如何,完整的链接会更好地抵抗链接腐烂。
本文收集自互联网,转载请注明来源。
如有侵权,请联系 [email protected] 删除。
我来说两句