【问题标题】:128-bit division intrinsic in Visual C++Visual C++ 中的 128 位除法内在函数
【发布时间】:2012-01-17 04:44:12
【问题描述】:

我想知道 Visual C++ 中是否真的没有 128 位除法内在函数?

有一个名为 _umul128() 的 64x64=128 位乘法内在函数,它与 MUL x64 汇编指令非常匹配。

当然,我假设也会有一个 128/64=64 位除法内在函数(模拟 DIV 指令),但令我惊讶的是,Visual C++ 和 Intel C++ 似乎都没有,至少它没有列出在 intrin.h 中。

有人可以确认吗?我尝试 grep'ing 编译器可执行文件中的函数名称,但一开始找不到 _umul128,所以我想我看错了位置。

更新:至少我现在在 Visual C++ 2010 的 c1.dll 中找到了模式 umul128(没有前导下划线)。所有其他内在函数都在它周围列出,但不幸的是没有“udiv128”等: (所以看起来他们真的“忘记了”去实现它。

澄清一下:我不仅在寻找 128 位数据类型,而且还在寻找一种在 C++ 中将 128 位标量 int 除以 64 位 int 的方法。 intrinsic functionnative 128 位整数支持都可以解决我的问题。

编辑:答案是否定的,直到 2017 年 Visual Studio 2010 中都没有 _udiv128 内在函数,但它在 Visual Studio 2019 RTM 中可用

【问题讨论】:

  • 它不是 CRT 的一部分。它是内在的,随处理器免费提供。但仅限于 64 位模式。除非您获得 128 位处理器,否则 div 没有免费赠品。鉴于 pow(2, 128) 的范围非常大,您应该寻找任意精度库。周围有很多人。
  • @TreeMonkie: VS 不支持 __int18,见stackoverflow.com/questions/6759592/…
  • @Hans:对不起,我不明白。它只是不是内在的,即使在 64 位模式下也是如此。我需要它来编写一个任意精度库:)
  • 好吧,那么寻找盒装解决方案毫无意义。你从小学就知道如何用纸和铅笔做任意精确的数学。 128 位需要大量纸张,但计算机有很多。
  • @cxxl:我相信不直接支持 128 位 int ......但是您可以在使用 SSE 内在函数时使用它们。我相信——但不要引用我的话——它是__m128。对于 SSE 是否会在这种情况下使用的问题,我并不完全清楚......

标签: visual-c++ intrinsics integer-division 128-bit


【解决方案1】:

如果您不介意小技巧,这可能会有所帮助(仅限 64 位模式,未经测试):

#include <windows.h>
#include <stdio.h>

unsigned char udiv128Data[] =
{
  0x48, 0x89, 0xD0, // mov rax,rdx
  0x48, 0x89, 0xCA, // mov rdx,rcx
  0x49, 0xF7, 0xF0, // div r8
  0x49, 0x89, 0x11, // mov [r9],rdx
  0xC3              // ret
};

unsigned char sdiv128Data[] =
{
  0x48, 0x89, 0xD0, // mov rax,rdx
  0x48, 0x89, 0xCA, // mov rdx,rcx
  0x49, 0xF7, 0xF8, // idiv r8
  0x49, 0x89, 0x11, // mov [r9],rdx
  0xC3              // ret
};

unsigned __int64 (__fastcall *udiv128)(unsigned __int64 numhi,
                                       unsigned __int64 numlo,
                                       unsigned __int64 den,
                                       unsigned __int64* rem) =
  (unsigned __int64 (__fastcall *)(unsigned __int64,
                                   unsigned __int64,
                                   unsigned __int64,
                                   unsigned __int64*))udiv128Data;

__int64 (__fastcall *sdiv128)(__int64 numhi,
                              __int64 numlo,
                              __int64 den,
                              __int64* rem) =
  (__int64 (__fastcall *)(__int64,
                          __int64,
                          __int64,
                          __int64*))sdiv128Data;

int main(void)
{
  DWORD dummy;
  unsigned __int64 ur;
  __int64 sr;
  VirtualProtect(udiv128Data, sizeof(udiv128Data), PAGE_EXECUTE_READWRITE, &dummy);
  VirtualProtect(sdiv128Data, sizeof(sdiv128Data), PAGE_EXECUTE_READWRITE, &dummy);
  printf("0x00000123456789ABCDEF000000000000 / 0x0001000000000000 = 0x%llX\n",
         udiv128(0x00000123456789AB, 0xCDEF000000000000, 0x0001000000000000, &ur));
  printf("-6 / -2 = %lld\n",
         sdiv128(-1, -6, -2, &sr));
  return 0;
}

【讨论】:

  • 对于 MSVC,可以使用#pragma 部分在编译期间将这些函数放入代码段
  • 为什么不能使用内联汇编?
  • @SandeepDatta 在 64 位代码中编译器不支持它。现在支持吗?
  • 强烈推荐const unsigned char code[];你希望它是const,所以它进入.rdata。我不知道它是否已经在代码部分旁边,因此是可执行的,就像.rodata 进入 Linux/ELF 上的 TEXT 段一样,但它应该会有所帮助。并制作函数指针 const 或静态 const(或 constexpr),以便它们可以(希望)被优化掉,而不是编译为实际的内存间接调用。 与单独编译的 .asm 文件相比,将它们放入数组中确实没有任何好处。如果调用编译为间接调用,则纯粹是缺点。
  • 另外,颠倒前 2 个 args 的顺序,使高半部分已经在 RDX 中。 (如果您希望源具有hi,lo, den,您可以编写一个内联包装函数来优化,以隐藏此细节。)
【解决方案2】:

一个小改进——少了一条指令

extern "C" digit64 udiv128(digit64 low, digit64 hi, digit64 divisor, digit64 *remainder);

; Arguments
; RCX       Low Digit
; RDX       High Digit
; R8        Divisor
; R9        *Remainder

; RAX       Quotient upon return

.code
udiv128 proc
    mov rax, rcx    ; Put the low digit in place (hi is already there)
    div r8      ; 128 bit divide rdx-rax/r8 = rdx remainder, rax quotient
    mov [r9], rdx   ; Save the reminder
    ret     ; Return the quotient
udiv128 endp
end

【讨论】:

    【解决方案3】:

    它现在可用。你可以使用_div128_udiv128

    _div128 内在函数将 128 位整数除以 64 位整数。返回值保存商,内在函数通过指针参数返回余数。 _div128 是 Microsoft 特定的。

    据说去年可以使用from "Dev16",但我不确定是哪个版本。我猜它是 VS 16.0 A.K.A VS2019,但 MSDN 上的文档显示它更进一步 VS2015

    【讨论】:

    • 根据文档,它在 Visual Studio 2019 RTM 中可用。我刚刚测试过它在 Visual Studio 2017 中尚不可用,resp。编译器版本 19.16.27030.1。
    【解决方案4】:

    我不是专家,但我发现了这个:

    http://research.swtch.com/2008/01/division-via-multiplication.html

    有趣的东西。希望对您有所帮助。

    编辑:这也很有见地:http://www.gamedev.net/topic/508197-x64-div-intrinsic/

    【讨论】:

    • 其实挺痛苦的。即使您发现需要倒数 + 移位,您也必须将 128 位标称乘以倒数并从结果中取出前 64 位,这是一个严重的 PITA
    • 我也很难相信整个事情会以某种方式胜过 DIV/IDIV 指令。
    【解决方案5】:

    感谢@alexey-frunze,它对 VS2017 进行了少许调整,检查了与 VS2019 相同的参数:

    #include <iostream>
    #include <string.h>
    #include <math.h>
    #include <immintrin.h>
    #define no_init_all
    #include <windows.h>
    
    unsigned char udiv128Data[] =
    {
        0x48, 0x89, 0xD0, // mov rax,rdx
        0x48, 0x89, 0xCA, // mov rdx,rcx
        0x49, 0xF7, 0xF0, // div r8
        0x49, 0x89, 0x11, // mov [r9],rdx
        0xC3              // ret
    };
    
    unsigned char sdiv128Data[] =
    {
        0x48, 0x89, 0xD0, // mov rax,rdx
        0x48, 0x89, 0xCA, // mov rdx,rcx
        0x49, 0xF7, 0xF8, // idiv r8
        0x49, 0x89, 0x11, // mov [r9],rdx
        0xC3              // ret
    };
    
    unsigned __int64(__fastcall* udiv128)(
        unsigned __int64 numhi,
        unsigned __int64 numlo,
        unsigned __int64 den,
        unsigned __int64* rem) =
        (unsigned __int64(__fastcall*)(
            unsigned __int64,
            unsigned __int64,
            unsigned __int64,
            unsigned __int64*))
            ((unsigned __int64*)udiv128Data);
    
    __int64(__fastcall *sdiv128)(
        __int64 numhi,
        __int64 numlo,
        __int64 den,
        __int64* rem) =
        (__int64(__fastcall *)(
            __int64,
            __int64,
            __int64,
            __int64*))
            ((__int64*)sdiv128Data);
    
    void test1()
    {
        unsigned __int64 a = 0x3c95ba9e6a637e7;
        unsigned __int64 b = 0x37e739d13a6d036;
        unsigned __int64 c = 0xa6d036507ecc7a7;
        unsigned __int64 d = 0x7ecc37a70c26e68;
        unsigned __int64 e = 0x6e68ac7e5f15726;
    
        DWORD dummy;
        VirtualProtect(udiv128Data, sizeof(udiv128Data), PAGE_EXECUTE_READWRITE, &dummy);
        e = udiv128(a, b, c, &d);
    
        printf("d = %llx, e = %llx\n", d, e);    // d = 1ed37bdf861c50, e = 5cf9ffa49b0ec9aa
    
    }
    
    void test2()
    {
        __int64 a = 0x3c95ba9e6a637e7;
        __int64 b = 0x37e739d13a6d036;
        __int64 c = 0xa6d036507ecc7a7;
        __int64 d = 0x7ecc37a70c26e68;
        __int64 e = 0x6e68ac7e5f15726;
    
        DWORD dummy;
        VirtualProtect(sdiv128Data, sizeof(sdiv128Data), PAGE_EXECUTE_READWRITE, &dummy);
        e = sdiv128(a, b, c, &d);
    
        printf("d = %llx, e = %llx\n", d, e);    // d = 1ed37bdf861c50, e = 5cf9ffa49b0ec9aa
    
    }
    
    int main()
    {
        test1();
        test2();
    
        return 0;
    }
    

    【讨论】:

      猜你喜欢
      • 2015-12-09
      • 1970-01-01
      • 2012-06-30
      • 2015-10-06
      • 1970-01-01
      • 2011-11-17
      • 2022-06-17
      • 2018-03-12
      • 2013-08-28
      相关资源
      最近更新 更多