使用 .NET Core 的硬件内部函数将 64 位整数相乘

Coc*_*lla 5 c# math intrinsics .net-core .net-core-3.0

我正在编写一些性能敏感的代码,其中无符号 64 位整数 ( ulong) 的乘法是一个瓶颈。

.NET Core 3.0 可以使用System.Runtime.Intrinsics命名空间访问硬件内部函数,这太棒了。

我目前正在使用一个可移植的实现,它返回 128 位结果的高位和低位元组:

[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static unsafe (ulong Hi, ulong Lo) Multiply64(ulong x, ulong y)
{
    ulong hi;
    ulong lo;

    lo = x * y;

    ulong x0 = (uint)x;
    ulong x1 = x >> 32;

    ulong y0 = (uint)y;
    ulong y1 = y >> 32;

    ulong p11 = x1 * y1;
    ulong p01 = x0 * y1;
    ulong p10 = x1 * y0;
    ulong p00 = x0 * y0;

    // 64-bit product + two 32-bit values
    ulong middle = p10 + (p00 >> 32) + (uint)p01;

    // 64-bit product + two 32-bit values
    hi = p11 + (middle >> 32) + (p01 >> 32);

    return (hi, lo);
}
Run Code Online (Sandbox Code Playgroud)

我想使用内在函数加快速度。我很清楚如何在可用时使用 BMI2(这比便携式版本快约 50%):

ulong lo;
ulong hi = System.Runtime.Intrinsics.X86.Bmi2.X64.MultiplyNoFlags(x, y, &lo);
return (hi, lo);
Run Code Online (Sandbox Code Playgroud)

我完全不清楚如何使用其他可用的内在函数;他们似乎都依赖Vector<128>类型,似乎没有一个人处理ulong类型。

如何ulong使用 SSE、AVX 等实现s 的乘法?

Pet*_*des 4

SIMD 向量不是单宽整数。最大元素宽度为 64 位。它们用于并行处理多个元素。

x86 没有任何 64x64 => 128 位 SIMD 元素乘法指令,即使使用 AVX512DQ 也是如此。 (这确实为 2、4 或 8 个并行元素提供了 SIMD 64x64 => 64 位乘法。)

AVX512IFMA(在 Cascade Lake 中)具有 52 位高半和低半乘法累加(这并非巧合,即 的有效数宽度double;SIMD 整数乘法指​​令使用与 FP 相同的乘法硬件)。


因此,如果您想要 64x64 => 128 位 SIMD 乘法,则必须从 4x 32x32 => 64 位vpmuludq和一些加法中合成它,包括加法宽度进位,您必须再次从多个指令合成它。

mul r64即使 AVX512 可用,这也可能比乘法数组的标量慢。只需 4 条标量mul指令即可产生 512 位乘法结果,而现代 x86 CPU 完全流水线化mul,因此每个时钟可以产生 1 对结果。(当然,在 IceLake / Sunny Cove 之前,存储吞吐量仅为每个时钟 1,因此存储 64 位结果的两半是一个问题!但是,将数据移动到 128 位存储的 XMM 寄存器会花费更多的 uops,并且也会造成每个时钟 64 位瓶颈。)

如果您只需要 64x64 => 64 位乘法,则可以删除high32*high32乘法。我以最快的方式编写了一个 C++ 版本来乘以 int64_t 数组?它在带有 AVX2 的 Haswell 上比标量快一点,但在 Skylake 上要快得多。不管怎样,如果没有 AVX2,一切都是不值得的。


顺便说一句,您不需要 BMI2 来执行标量 64x64 => 128 位乘法

这是 x86-64 的基线,具有单操作数mul(无符号)或imul(有符号)。如果 C# 公开BMI2mulx的内在函数,那么它肯定必须公开普通无符号mul和有符号的内在函数imul,这在大多数情况下至少同样有效(并且代码大小更小)。

  • 有与 128 位 `mul` 支持相关的各种问题:[提高 x64 平台的 System.Decimal 性能](https://github.com/dotnet/coreclr/issues/10642)、[启用长乘法](https: //github.com/dotnet/coreclr/pull/7065),https://github.com/dotnet/coreclr/pull/21362#discussion_r239273064,https://github.com/dotnet/corefx/issues/32075#issuecomment -420467575 (2认同)
  • @PeterCordes 我的错字。我的意思是 .NET core 中没有“mul”或“imul”的内在特性。只有“mulx” (2认同)
  • @phuclv:那太愚蠢了。并非所有 C 编译器都有一个,但主要的编译器没有内在函数,而是支持 128 位整数类型,因此可以从“a * (__int128)b”发出它。但是在 GCC 中出现“__int128”类型之前,有一段时间已经存在 64 位平台,所以我猜 .NET core 正处于其发展的那个阶段? (2认同)