如何指示MSVC编译器使用64位/ 32位除法而不是较慢的128位/ 64位除法?

乔治·罗宾逊

我如何告诉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也不会溢出,并且可能已经编译mainmov 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] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章