【问题标题】:MSVC's instrinsics __emulu and _umul128 in GCC/CLangMSVC 在 GCC/CLang 中的内在函数 __emulu 和 _umul128
【发布时间】:2021-08-12 18:25:04
【问题描述】:

在 MSVC 中存在内部函数 __emulu()_umul128()。第一个是u32*u32->u64 乘法,第二个是u64*u64->u128 乘法。

CLang/GCC 是否存在相同的内在函数?

我找到的最接近的是Intel's Guide 中提到的_mulx_u32()_mulx_u64()。但是他们产生需要 BMI2 支持的mulx 指令。而 MSVC 的内在函数产生常规的 mul 指令。还有_mulx_u32()-m64模式下不可用,而__emulu()_umul128()在MSVC的32位和64位模式下都存在。

您可以在线尝试32-bit code64-bit code

对于 32 位用户,可能会使用 return uint64_t(a) * uint64_t(b);(参见 online),希望编译器能够正确猜测并优化为使用 u32*u32->u64 乘法而不是 u64*u64->u64。但是有没有办法确定这一点?不要依赖编译器猜测两个参数都是 32 位的(即 uint64_t 的较高部分为零)?拥有一些像 __emulu() 这样的内在函数来确保你对代码的了解。

在 GCC/CLang 中有 __int128(参见代码 online),但我们不得不再次依赖编译器的猜测,即我们实际上乘以 64 位数字(即 int128 的较高部分为零)。如果存在一些内在函数,有没有办法在没有编译器猜测的情况下确定?

顺便说一句,uint64_t(32 位)和__int128(64 位)在 GCC/CLang 中生成正确的 mul 指令而不是 mulx。但我们再次必须依赖编译器正确猜测uint64_t__int128 的较高部分为零。

当然,我可以查看 GCC/Clang 已优化并正确猜测的汇编程序代码,但查看一次汇编程序并不能保证在所有情况下都会发生相同的情况。而且我不知道在 C++ 中静态断言编译器对汇编指令的猜测正确的方法。

【问题讨论】:

  • github.com/yuikns/intrin/blob/master/intrin_x86.h#L769 ?我花了不到 3 分钟就找到了。 Do same intrinsics exist for CLang/GCC?您是否阅读了编译器文档来检查自己?前任。这里:gcc.gnu.org/onlinedocs/gcc-11.1.0/gcc/…Is there a way to be sure without compiler guessing through using some intrinsic? 不,“内在”这个名字已经意味着它依赖于编译器。
  • @KamilCuk 谢谢!通过汇编程序看起来像是一个不错的解决方案。如果您将 __emulu()_umul128() 的汇编代码作为答案发布,那就太好了。我可以接受。
  • @KamilCuk 我也看不到 128 位版本。你知道 128 位的汇编代码吗?
  • @KamilCuk 因此,如果您知道如何在 GCC/Clang 汇编程序中为 32 位和 64 位的解决方案编写代码,请发布此 asm 作为我问题的答案。

标签: c++ 64-bit multiplication 32-bit intrinsics


【解决方案1】:

你已经有了答案。使用uint64_t__uint128_t。不需要内在函数。这适用于所有 64 位目标的现代 GCC 和 Clang。见Is there a 128 bit integer in gcc?

#include <stdint.h>
typedef __uint128_t uint128_t;

// 32*32=64 multiplication
f(uint32_t a, uint32_t b) {
   uint64_t ab = (uint64_t)a * b;
}

//64*64=128 multiplication
f(uint64_t a, uint64_t b) {
    uint128_t ab = (uint128_t)a * b;
}

请注意,强制转换必须在操作数上,或至少在一个操作数上。转换结果是行不通的,因为它会用较短的类型进行乘法运算并扩展结果。

但是有没有办法确定这一点?不要依赖编译器的猜测

您得到与编译器内在函数完全相同的保证:结果的值是正确的。从来没有任何关于优化的保证。仅仅因为您使用了内在函数并不能保证编译器会发出“明显的”汇编指令。获得这种保证的唯一方法是使用内联汇编,对于像这样的简单操作,它可能会损害性能,因为它会限制编译器优化寄存器使用的方式。

【讨论】:

  • 据我所知,标准中没有限制说uint64_t(a) * uint64_t(b) 应该总是做短乘法,以防ab 都是 32 位,即更高的部分uint64_t(a) 归零。编译器可以很容易地进行长算术全乘法。而且我的库中有非常敏感且性能要求很高的代码。这就是为什么我需要确定。您是否知道 GCC/CLang 汇编代码可以严格按照我的意愿执行并且确实可以确定?即使这会禁止其他编译器的优化。
  • @Arty 如果您的代码对性能敏感,您需要在目标处理器(不是其他模型)上对代码(不是一些玩具示例)进行基准测试。内存访问的成本远高于乘法,因此内部循环的寄存器分配和代码大小往往比算术更重要。
  • 如果编译器不小心决定进行长算术乘法(多指令)而不是单指令mul 乘法,那么无论用户如何使用我的库函数,在所有情况下都会变得更糟,因为长算术会包含相同的mul 用于低字计算,因此这个长版本将始终是短版本的过度集,因此总是需要更多时间。不,最终程序不受内存限制,预计它会使用更多的算术计算而不是随机内存访问。
  • 而我的函数是一个库函数,我无法访问最终程序,甚至无法访问最终的CPU架构,因此我无法控制和优化最终程序,它会只需使用我的库函数,我的责任是使其尽可能高性能,并确保它始终独立于编译器的猜测执行。我唯一知道的是最终程序是受计算限制的,而不是受内存限制的。
  • @arty 如果你不知道目标架构,你怎么知道它会支持__int128?即使使用 GCC,它的支持也是有限的。
猜你喜欢
  • 1970-01-01
  • 2018-07-12
  • 2023-03-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-01-04
  • 2022-10-18
  • 1970-01-01
相关资源
最近更新 更多