发出 DIV 指令,而不是 __udivti3
Emit DIV instruction, instead of __udivti3
考虑以下代码:
unsigned long long div(unsigned long long a, unsigned long long b, unsigned long long c) {
unsigned __int128 d = (unsigned __int128)a*(unsigned __int128)b;
return d/c;
}
当使用 x86-64 gcc 10 或 clang 10 编译时,都使用 -O3
,它 emits __udivti3
,而不是 DIVQ
指令:
div:
mov rax, rdi
mov r8, rdx
sub rsp, 8
xor ecx, ecx
mul rsi
mov r9, rax
mov rsi, rdx
mov rdx, r8
mov rdi, r9
call __udivti3
add rsp, 8
ret
至少在我的测试中,前者比后者(已经)慢得多,因此问题是:有没有办法让现代编译器为上述代码发出 DIVQ
?
编辑:假设商适合 64 位寄存器。
如果商不适合 64 位,div
将出错。在一般情况下,使用 mul + 单个 div 执行 (a*b) / c
是不安全的(不会为每个可能的输入实现抽象机器语义),因此编译器无法以这种方式生成 asm对于 x86-64。
即使您确实给了编译器足够的信息来确定 division 不会溢出,不幸的是 gcc/clang 仍然不会将其优化为 div
具有非零高半 dividend (RDX)。
您需要一个内部或内联 asm 来显式执行 128 / 64 位 => 64 位 division。例如Intrinsics for 128 multiplication and division 有 GNU C 内联汇编,分别看起来适合 low/high 一半。
不幸的是,GNU C 没有这个的内在函数。不过,MSVC 确实如此:Unsigned 128-bit division on 64-bit machine 有链接。
考虑以下代码:
unsigned long long div(unsigned long long a, unsigned long long b, unsigned long long c) {
unsigned __int128 d = (unsigned __int128)a*(unsigned __int128)b;
return d/c;
}
当使用 x86-64 gcc 10 或 clang 10 编译时,都使用 -O3
,它 emits __udivti3
,而不是 DIVQ
指令:
div:
mov rax, rdi
mov r8, rdx
sub rsp, 8
xor ecx, ecx
mul rsi
mov r9, rax
mov rsi, rdx
mov rdx, r8
mov rdi, r9
call __udivti3
add rsp, 8
ret
至少在我的测试中,前者比后者(已经)慢得多,因此问题是:有没有办法让现代编译器为上述代码发出 DIVQ
?
编辑:假设商适合 64 位寄存器。
div
将出错。在一般情况下,使用 mul + 单个 div 执行 (a*b) / c
是不安全的(不会为每个可能的输入实现抽象机器语义),因此编译器无法以这种方式生成 asm对于 x86-64。
即使您确实给了编译器足够的信息来确定 division 不会溢出,不幸的是 gcc/clang 仍然不会将其优化为 div
具有非零高半 dividend (RDX)。
您需要一个内部或内联 asm 来显式执行 128 / 64 位 => 64 位 division。例如Intrinsics for 128 multiplication and division 有 GNU C 内联汇编,分别看起来适合 low/high 一半。
不幸的是,GNU C 没有这个的内在函数。不过,MSVC 确实如此:Unsigned 128-bit division on 64-bit machine 有链接。