扩展除法/乘法的 gcc 内在函数

Posted

技术标签:

【中文标题】扩展除法/乘法的 gcc 内在函数【英文标题】:gcc intrinsic for extended division/multiplication 【发布时间】:2012-11-02 00:35:42 【问题描述】:

现代 CPU 可以在两个原生大小的字之间执行扩展乘法,并将低位和高位结果存储在单独的寄存器中。同样,在执行除法时,它们将商和余数存储在两个不同的寄存器中,而不是丢弃不需要的部分。

是否存在某种可移植的 gcc 内在函数,它会采用以下签名:

void extmul(size_t a, size_t b, size_t *lo, size_t *hi);

或者类似的东西,对于除法:

void extdiv(size_t a, size_t b, size_t *q, size_t *r);

我知道我可以通过在代码中添加#ifdef 来通过内联汇编和鞋拔可移植性自行完成,或者我可以使用部分和来模拟乘法部分(这会慢得多),但我想避免这种情况为了可读性。肯定有一些内置函数可以做到这一点?

【问题讨论】:

但是当使用-O3 进行优化时,GCC 可能会发出正确的特定于处理器的扩展乘法或除法(甚至可能同时使用模数和余数,如果您的程序同时使用两者),所以我不会打扰在你的代码中。 @BasileStarynkevitch 但是您需要 128 位整数支持 - GCC 没有 x64 支持。您无法在 C 中仅使用 64 位整数“欺骗”GCC 以发出 64x64 -> 128 位乘法或 128 位/64 位 -> 64 位除法。 如果您需要 128 位,gcc 可能有一些扩展类型,例如 __int128 或者可以在没有 cpu 特定代码的情况下为您提供所需效果的东西。对于 64 位(在 32 位机器上),只需使用 long long 即可。 FWIW,MSVC 具有您正在寻找的扩展乘法的内在特性 (64x64 ->128)。在 GCC x64 中,我只使用内联汇编宏。 @Mystical 您提供的链接中的“整数模式”与本机整数宽度不对应,而是“机器模式”。大多数 64 位目标实际上都支持 QImode,即 128 位。特别是在 x86_64 128 位整数上工作得很好。 【参考方案1】:

对于 4.6 版以来的 gcc,您可以使用 __int128。这适用于大多数 64 位硬件。比如

要获得 64x64 位乘法的 128 位结果,只需使用

void extmul(size_t a, size_t b, size_t *lo, size_t *hi) 
    __int128 result = (__int128)a * (__int128)b;
    *lo = (size_t)result;
    *hi = result >> 64;

在 x86_64 上,gcc 足够聪明,可以将其编译为

   0:   48 89 f8                mov    %rdi,%rax
   3:   49 89 d0                mov    %rdx,%r8
   6:   48 f7 e6                mul    %rsi
   9:   49 89 00                mov    %rax,(%r8)
   c:   48 89 11                mov    %rdx,(%rcx)
   f:   c3                      retq   

不需要原生 128 位支持或类似支持,在内联后只剩下 mul 指令。

编辑:在 32 位架构上,这以类似的方式工作,您需要将 __int128_t 替换为 uint64_t 并将移位宽度替换为 32。优化将适用于更旧的 gcc。

【讨论】:

那么,如果不使用一堆#ifdef,真的没有办法让任何字长(不仅仅是64位)都能正常工作吗?如果没有,我会接受这个答案.. 如果您需要 64 位结果的两个 32 位部分,这将以类似的方式在 32 位拱门上工作。您需要将移位宽度设为 32 位 - 我确信在 stdlib 中定义了一个适当的宏 @Thomas 可能缺少的可能是 dsize_t 之类的东西,这将是一个双宽度 size_t 类型 - 就像 64/32 位拱门上的 __int128uint64_t。跨度> @BasileStarynkevitch 刚刚测试过,gcc-4.5 不知道 128bit ints,gcc-4.6 可以。 为了完整性:英特尔 ICC 版本 13.0 也可以识别 __int128(和/或 __int128_t),而 ICC 12.1 及更低版本不知道。【参考方案2】:

对于那些想知道问题的另一半(除法)的人,gcc 没有为此提供内在函数,因为处理器除法指令不符合标准。

对于 64 位 x86 目标的 128 位除数和 32 位 x86 目标的 64 位除数都是如此。问题是,在标准规定结果应该被截断的情况下,DIV 会导致除法溢出异常。例如(unsigned long long) (((unsigned _int128) 1 << 64) / 1) 应该计算为 0,但如果使用 DIV 计算会导致除法溢出异常。

(感谢@ross-ridge 提供此信息)

【讨论】:

这是不将 64/32 优化为 idiv 的原因,而不是不提供内在函数 的原因。 GCC 有许多 __builtin 函数调用带有一些参数的未定义行为。

以上是关于扩展除法/乘法的 gcc 内在函数的主要内容,如果未能解决你的问题,请参考以下文章

高精度乘法和除法

数论笔记-同余

模意义下的除法变乘法

SSE整数除法?

Excel怎么进行乘法和除法

(母函数 Catalan数 大数乘法 大数除法) Train Problem II hdu1023