【问题标题】:Visual C++ x64 add with carryVisual C++ x64 添加带进位
【发布时间】:2012-02-27 01:46:32
【问题描述】:

由于似乎没有 ADC 的内在函数,而且我不能在 Visual C++ 中使用 x64 架构的内联汇编程序,如果我想使用带进位的 add 编写函数但将其包含在C++ 命名空间?

(不能使用比较运算符进行模拟。这个 256 兆位的添加对性能至关重要。)

【问题讨论】:

  • 告诉我们更多关于这个“256 兆位添加”的信息。即使考虑到进位必须作为额外的步骤来处理,使用 SIMD 一次执行多个加法很可能会相当快。
  • 我已经做了一些研究。见stackoverflow.com/questions/8866973/…
  • @jnm2 - x64 方式似乎正在编写单独的汇编代码并从您的 C++ 函数中调用它。汇编器已经是包的一部分。

标签: c++ visual-c++ 64-bit inline-assembly intrinsics


【解决方案1】:

VS2010 内置支持编译和链接以汇编语言编写并由 MASM (ml64.exe) 翻译的代码。您只需跳过几圈即可启用它:

  • 右键单击解决方案资源管理器窗口中的项目,构建自定义项,勾选“masm”。
  • 项目 + 添加新项目,选择 C++ 文件模板,但将其命名为 something.asm
  • 确保您已获得项目的 x64 平台目标。 Build + Configuration Manager,在“Active solution platform”组合中选择“x64”。如果缺少,请选择 <New> 并从第一个组合中选择 x64。如果丢失,则必须重新运行设置并添加对 64 位编译器的支持。

使用 MASM 语法编写汇编代码,参考 is here。快速入门教程is here.

汇编代码的骨架如下所示:

.CODE
PUBLIC Foo
Foo PROC
  ret                    ; TODO: make useful
Foo ENDP
END

并像这样从 C++ 代码中调用:

extern "C" void Foo();

int main(int argc, char* argv[])
{
    Foo();
    return 0;
}

提供完整的调试支持,您通常希望至少使用“调试 + Windows + 寄存器”窗口。

【讨论】:

  • 在这种情况下,理想的解决方案是内联函数(内联汇编)。在目标文件中使用汇编器和链接不会这样做,并且 MSVC 中的 64 位代码不允许内联汇编。因此,这意味着 OP 还必须在汇编中编写许多其他函数(编译器可能已经很好地完成了这些函数)以避免函数调用。
【解决方案2】:

我已经使用unsigned long long 的数组实现了一个 256 位整数,并使用 x64 程序集来实现带进位的加法。这是 C++ 调用者:

#include "stdafx.h"

extern "C" void add256(unsigned long long *a, unsigned long long * b, unsigned long long *c);

int _tmain(int argc, _TCHAR* argv[])
{
    unsigned long long a[4] = {0x8000000000000001, 2, 3, 4};
    unsigned long long b[4] = {0x8000000000000005, 6, 7, 8};
    unsigned long long c[4] = {0, 0, 0, 0};
    add256(a, b, c); // c[] == {6, 9, 10, 12};
    return 0;
}

add256 在汇编中实现:

    ; void add256(unsigned long long *a, unsigned long long * b, unsigned long long *c)

.CODE
PUBLIC add256
add256 PROC

    mov                 qword ptr [rsp+18h],r8    
    mov                 qword ptr [rsp+10h],rdx    
    mov                 qword ptr [rsp+8],rcx    
    push                rdi    

    ; c[0] = a[0] + b[0];

    mov                 rax,qword ptr 16[rsp]
    mov                 rax,qword ptr [rax]    
    mov                 rcx,qword ptr 24[rsp]
    add                 rax,qword ptr [rcx]    
    mov                 rcx,qword ptr 32[rsp]
    mov                 qword ptr [rcx],rax    

    ; c[1] = a[1] + b[1] + CARRY;

    mov                 rax,qword ptr 16[rsp]
    mov                 rax,qword ptr [rax+8]    
    mov                 rcx,qword ptr 24[rsp]
    adc                 rax,qword ptr [rcx+8]    
    mov                 rcx,qword ptr 32[rsp]
    mov                 qword ptr [rcx+8],rax    

    ; c[2] = a[2] + b[2] + CARRY;

    mov                 rax,qword ptr 16[rsp]
    mov                 rax,qword ptr [rax+10h]    
    mov                 rcx,qword ptr 24[rsp]
    adc                 rax,qword ptr [rcx+10h]    
    mov                 rcx,qword ptr 32[rsp]
    mov                 qword ptr [rcx+10h],rax    

    ; c[3] = a[3] + b[3] + CARRY;

    mov                 rax,qword ptr 16[rsp]
    mov                 rax,qword ptr [rax+18h]    
    mov                 rcx,qword ptr 24[rsp]
    adc                 rax,qword ptr [rcx+18h]    
    mov                 rcx,qword ptr 32[rsp]
    mov                 qword ptr [rcx+18h],rax    

    ; }

    pop                 rdi    
    ret    

    add256              endp

    end                        

我知道您表示您不想要一个带进位的模拟加法解决方案,并且想要一个高性能的解决方案,但是,您仍然可以考虑以下仅 C++ 的解决方案,它具有模拟 256 位数字的好方法:

#include "stdafx.h"

int _tmain(int argc, _TCHAR* argv[])
{
    unsigned long long a[4] = {0x8000000000000001, 2, 3, 4};
    unsigned long long b[4] = {0x8000000000000005, 6, 7, 8};
    unsigned long long c[4] = {0, 0, 0, 0};
    c[0] = a[0] + b[0]; // 6
    c[1] = a[1] + b[1] + (c[0] < a[0]); // 9
    c[2] = a[2] + b[2] + (c[1] < a[1]); // 10
    c[3] = a[3] + b[3] + (c[2] < a[2]); // 12
    return 0;
}

【讨论】:

  • 抱歉来晚了,但是c++解决方案不正确。为简化考虑 a = 01 和 b=11 进位 = 1,然后 c=01 进位 = 1 但 c
【解决方案3】:

There is now an instrinsic 用于 MSVC 中的 ADC_addcarry_u64。以下代码

#include <inttypes.h>
#include <intrin.h>
#include <stdio.h>

typedef struct {
    uint64_t x1;
    uint64_t x2;
    uint64_t x3;
    uint64_t x4;
} uint256;

void add256(uint256 *x, uint256 *y) {
    unsigned char c = 0;
    c = _addcarry_u64(c, x->x1, y->x1, &x->x1);
    c = _addcarry_u64(c, x->x2, y->x2, &x->x2);
    c = _addcarry_u64(c, x->x3, y->x3, &x->x3);
    _addcarry_u64(c, x->x4, y->x4, &x->x4);
}

int main() {
    //uint64_t x1, x2, x3, x4;
    //uint64_t y1, y2, y3, y4;
    uint256 x, y;
    x.x1 = x.x2 = x.x3 = -1; x.x4 = 0;
    y.x1 = 2; y.x2 = y.x3 = y.x4 = 0;

    printf(" %016" PRIx64 "%016" PRIx64 "%016" PRIx64 "%016" PRIx64 "\n", x.x4, x.x3, x.x2, x.x1);
    printf("+");
    printf("%016" PRIx64 "%016" PRIx64 "%016" PRIx64 "%016" PRIx64 "\n", y.x4, y.x3, y.x2, y.x1);
    add256(&x, &y);
    printf("=");
    printf("%016" PRIx64 "%016" PRIx64 "%016" PRIx64 "%016" PRIx64 "\n", x.x4, x.x3, x.x2, x.x1);
}

从 Visual Studio Express 2013 生成以下程序集输出

mov rdx, QWORD PTR x$[rsp]
mov r8, QWORD PTR x$[rsp+8] 
mov r9, QWORD PTR x$[rsp+16]
mov rax, QWORD PTR x$[rsp+24]
add rdx, QWORD PTR y$[rsp]
adc r8, QWORD PTR y$[rsp+8]
adc r9, QWORD PTR y$[rsp+16]
adc rax, QWORD PTR y$[rsp+24]

其中有一个add 和三个adc 符合预期。

编辑:

_addcarry_u64 的作用似乎有些混乱。如果您查看我在此答案开头链接到的 Microsoft 文档,则表明它不需要任何特殊硬件。这会产生adc,它可以在所有x86-64 处理器上运行(_addcarry_u32 甚至可以在更旧的处理器上运行)。它在我测试过的 Ivy Bridge 系统上运行良好。

但是,_addcarryx_u64 确实需要adx(如 MSFT 的文档中所示),而且它确实无法在我的 Ivy Bridge 系统上运行。

【讨论】:

  • 此答案需要免责声明,此指令只能在第 4 代酷睿处理器(Haswell 及以上)上使用。再过 5 到 10 年和一个支持电话号码,您才能盲目依赖它。
  • @HansPassant 我无法确认。你有这方面的参考吗?
  • @HansPassant,你错了,这在我的 Ivy Bridge 系统上运行良好,它应该在所有 x86-64 处理器上运行良好。你看过我的回答吗?您是否注意到编译器生成了adc 而不是adcx?我更新了答案,以便您甚至可以在自己的系统上对其进行测试(我假设您可以在 Broadwell 之前找到一个)并查看生成的程序集。现在我编辑了我的问题,你可以投票给我。这是你能做的最起码的事情。
  • 好吧,我错了也没关系,这不是我的帖子。记录“第 4 代核心”集中的处理器是由您决定的。你的不是,所以你得到了旧的指令。我猜,不要很快得到Haswell。顺便说一句,非常好的处理器。
  • @HansPassant,ADX 指令被添加到 Broadwell,而不是 Haswell。我确实拥有一个 Haswell 处理器(但不是 Broadwell),但这不是我测试的。为什么要由我来记录哪些处理器是“第 4 代核心”。这与我的回答无关。 Maybe Intlel's documentation is wrong?.
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-05-08
  • 2011-08-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多