繁体   English   中英

在c ++中使用x86 DIV的asm块有什么用?

[英]What is the use of this asm block using x86 DIV in c++?

有人可以帮助我了解在性能方面使用asm块进行unsigned long long乘法的好处。 它与竞争性编程优化有关。 我猜想它使乘法运算更快,但是我实际上无法理解代码。

const int md = 998244353;
inline int mul(int a, int b)
{
#if !defined(_WIN32) || defined(_WIN64)
    return (int) ((long long) a * b % md);
#endif
    unsigned long long x = (long long) a * b;
    unsigned xh = (unsigned) (x >> 32), xl = (unsigned) x, d, m;
    asm(
            "divl %4; \n\t"
            : "=a" (d), "=d" (m)
            : "d" (xh), "a" (xl), "r" (md)
    );
    return m;
}

此代码实际上是32位(其中,所以编译器使用实际分割64×64 => 128乘法是不可用的加速,但在64位,其中编译器确实使用乘法逆完全避免慢硬件分割严重丧失。 为什么GCC在实现整数除法时使用乘以奇数的乘法?

另外,如果在内联和常量传播之后,任何一个输入都不是编译时常量,则它实际上应该使用__builtin_constant_p仅使用嵌入式asm。


但是无论如何, x86的div指令执行EDX:EAX / (src) =>商(EAX)和除数(EDX)。 请参阅何时以及为什么我们要对extend进行签名并在mul / div中使用cdq?

"a""d"约束分别要求EAX和EDX中64位乘积的低半和高半作为输入。

Godbolt编译器浏览器中

const int md = 998244353;
int mul(int a, int b)
{
#ifdef __x86_64__ // FIXME: just use the asm if defined(i386) to exclude all others
    return (int) ((long long) a * b % md);
#else
    if(__builtin_constant_p(a) && __builtin_constant_p(b))
        return (int) ((long long) a * b % md);
      // clang's builtin_constant_p is evaled before inlining, derp

    unsigned long long x = (long long) a * b;
    unsigned xh = (unsigned) (x >> 32), xl = (unsigned) x, d, m;
    asm(
            "divl %4; \n\t"
            : "=a" (d), "=d" (m)
            : "d" (xh), "a" (xl), "r" (md)
    );
    return m;
#endif
}

int main() {
    return mul(1,2);
}

使用gcc8.2 -O3 -m32编译如下:

mul(int, int):
    mov     eax, DWORD PTR [esp+8]
    mov     ecx, 998244353
    imul    DWORD PTR [esp+4]     # one-operand imul does EDX:EAX = EAX * src
    divl ecx;                     # EDX:EAX / ecx => EAX and EDX

    mov     eax, edx              # return the remainder
    ret

main:
    mov     eax, 2     # builtin_constant_p used the pure C, allowing constant-propagation
    ret

注意div无符号除法,因此它与C不匹配。C正在执行有符号乘法和有符号除法。 这可能应该使用idiv ,或将输入转换为无符号。 或者,也许由于某些奇怪的原因,他们确实确实想要带有负输入的怪异结果。

那么,为什么没有内联汇编程序,编译器为什么不能自己发出此消息? 因为如果商溢出目标寄存器(al / ax / eax / rax),它将以#DE(除法异常)错误而不是像其他所有整数指令一样默默地截断。

仅当您知道除数足够大以进行可能的除数时,64位/ 32位=> 32位除法才是安全的。 (但是即使是,gcc仍然不知道寻找这种优化。例如, a * 7ULL / 9则如果使用单个muldiv来完成,则可能不会导致#DE;如果a是32位类型的话。但是gcc仍会发出对libgcc helper函数的调用。)

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM