当前的编译器(gcc/clang/ICC/MSVC)不会从可移植的 ISO C 源代码中进行此优化,即使您让他们证明这一点b < a
所以商将适合 32 位。 (例如使用 GNU Cif(b>=a) __builtin_unreachable();
在戈德螺栓上)。这是一个错过的优化;在这个问题得到解决之前,您必须使用内在函数或内联汇编来解决它。
(或者使用 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 (Or on Godbolt.ms because recent MSVC on the main Godbolt site is not working1.)
#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
所以我们可以看到 MSVC 并没有通过_udiv64
,即使在这种情况下它不会溢出并且它可以编译main
只是mov eax, 0ccccccccH
/ ret
.
更新#2 https://godbolt.org/z/n3Dyp-添加了使用英特尔 C++ 编译器的解决方案,但这效率较低,并且会破坏常量传播,因为它是内联汇编。
#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/在真实 Windows 上托管最新的 MSVC 编译器,并且通常主要的 Godbolt.org 站点会转发到 MSVC 的站点。)
看来godbolt.ms会生成短链接,但不会再次扩展它们!无论如何,完整链接更好地抵抗链接腐烂。