当前位置: 首页 > 知识库问答 >
问题:

使用AVX2在装配x86_64中添加两个向量以及技术说明

鲁滨海
2023-03-14

我在这里做错了什么?我得到4个零,而不是:

2
4
6
8

我也很想修改我的. ami函数,以便通过更长的向量运行,因为为了在这里进行半衰期,我刚刚使用了一个具有四个元素的向量,这样我就可以在没有SIMD 256位寄存器循环的情况下对该向量进行求和。

#include <iostream>
#include <chrono>

extern "C" double *addVec(double *C, double *A, double *B, size_t &N);

int main()
{
    size_t N = 1 << 2;
    size_t reductions = N / 4;

    double *A = (double*)_aligned_malloc(N*sizeof(double), 32);
    double *B = (double*)_aligned_malloc(N*sizeof(double), 32);
    double *C = (double*)_aligned_malloc(N*sizeof(double), 32);

    for (size_t i = 0; i < N; i++)
    {
        A[i] = double(i + 1);
        B[i] = double(i + 1);
    }

    auto start = std::chrono::high_resolution_clock::now();

        double *out = addVec(C, A, B, reductions);

    auto finish = std::chrono::high_resolution_clock::now();

    for (size_t i = 0; i < N; i++)
    {
        std::cout << out[i] << std::endl;
    }

    std::cout << "\n\n";

    std::cout << std::chrono::duration_cast<std::chrono::nanoseconds>(finish - start).count() << " ns\n";

    std::cin.get();

    _aligned_free(A);
    _aligned_free(B);
    _aligned_free(C);

    return 0;
}
.data
; C -> RCX
; A -> RDX
; B -> r8
; N -> r9
.code
    addVec proc
        ;xor rbx, rbx
        align 16
        ;aIn:
            vmovapd ymm0, ymmword ptr [rdx]
            ;vmovapd ymm1, ymmword ptr [rdx + rbx + 4]
            vmovapd ymm2, ymmword ptr [r8]
            ;vmovapd ymm3, ymmword ptr [r8 + rbx + 4]

            vaddpd ymm0, ymm2, ymm3

            vmovapd ymmword ptr [rcx], ymm3
        ;inc rbx
        ;cmp rbx, qword ptr [r9]
        ;jl aIn
        mov rax, rcx    ; return the address of the output vector
    ret
    addVec endp
end

我还想得到一些其他的澄清:

> < li >我的CPU的每个内核有八个256位寄存器(ymm0-ymm7)还是总共有八个? < li >所有其他寄存器,如rax、rbx等...是总计还是每个核心? < li >由于我仅用SIMD协处理器和一个内核就可以每周期处理4个double,我能否用CPU的其余部分每周期执行另一条指令?例如,我可以用一个内核在每个周期添加5个double吗?(4同1) < li>

如果我没有在我的汇编函数中放入一个循环,而执行了如下操作,该怎么办?:

#pragma openmp并行

用于(size_t i=0;i

addVec(C i,A i,B i)

这是否会分叉coreNumber个超线程线程,并且每个线程在四个double上执行SIMD加法?所以每个周期总共4 * coreNumber double?我不能在这里添加超线程,对吗?

更新我可以这样做吗?:

.data
;// C -> RCX
;// A -> RDX
;// B -> r8
.code
    addVec proc
        ; One cycle 8 micro-op
            vmovapd ymm0, ymmword ptr [rdx]     ; 1 port
            vmovapd ymm1, ymmword ptr [rdx + 32]; 1 port
            vmovapd ymm2, ymmword ptr [r8]      ; 1 port
            vmovapd ymm3, ymmword ptr [r8 + 32] ; 1 port
            vfmadd231pd ymm0, ymm2, ymm4        ; 1 port
            vfmadd231pd ymm1, ymm3, ymm4        ; 1 port
            vmovapd ymmword ptr [rcx], ymm0     ; 1 port
            vmovapd ymmword ptr [rcx + 32], ymm1; 1 port

        ; Return the address of the output vector
        mov rax, rcx                            ; 1 port ?
    ret
    addVec endp
end

或者只是这个,因为我会超过你告诉我的六个端口?

.data
;// C -> RCX
;// A -> RDX
;// B -> r8
.code
    addVec proc
        ;align 16
        ; One cycle 5 micro-op ?
        vmovapd ymm0, ymmword ptr [rdx]     ; 1 port
        vmovapd ymm1, ymmword ptr [r8]      ; 1 port
        vfmadd231pd ymm0, ymm1, ymm2        ; 1 port
        vmovapd ymmword ptr [rcx], ymm0     ; 1 port

        ; Return the address of the output vector
        mov rax, rcx                        ; 1 port ?
    ret
    addVec endp
end

共有1个答案

秦博达
2023-03-14
匿名用户

代码得到错误结果的原因是程序集中的语法向后。

您使用的是英特尔语法,其中目标应位于源之前。因此,在原始的 .asm 代码中,您应该更改

vaddpd ymm0, ymm2, ymm3

 vaddpd ymm3, ymm2, ymm0

了解这一点的一种方法是使用内部函数,然后查看反汇编。

extern "C" double *addVec(double * __restrict C, double * __restrict A, double * __restrict B, size_t &N) {
    __m256d x = _mm256_load_pd((const double*)A);
    __m256d y = _mm256_load_pd((const double*)B);
    __m256d z = _mm256_add_pd(x,y);
    _mm256_store_pd((double*)C, z);
    return C;
}

在 Linux 上使用 g -S -O3 -mavx -masm=intel -mabi=ms foo 从 GCC 中分离出来.cpp给出:

vmovapd ymm0, YMMWORD PTR [rdx]
mov     rax, rcx
vaddpd  ymm0, ymm0, YMMWORD PTR [r8]
vmovapd YMMWORD PTR [rcx], ymm0
vzeroupper
ret

< code>vaddpd ymm0,ymm0,YMMWORD PTR [rdx]指令将加载和加法运算融合到一个融合的微操作中。当我对您的代码使用该函数时,它会得到2,4,6,8。

您可以找到将两个数组 xy 求和的源代码,并将其写出到 l1-内存带宽-50-效率下降-使用地址(差异为 4096)的数组 z。这使用内部函数和展开八次。使用 gcc -Sobjdump -d 对代码进行反化。另一个做几乎相同的事情并在汇编中编写的源是获取峰值带宽 -haswell-in-the-l1-cache-only-getting-62。在文件triad_fma_asm.asm 中,将行 pi: dd 3.14159 更改为 pi: dd 1.0。这两个示例都使用单个浮点,因此,如果您想要双精度,则必须进行必要的更改。

其他问题的答案如下:

    < li >处理器的每个内核都是一个物理上不同的单元,有自己的一组寄存器。每个内核有16个通用寄存器(例如rax、rbx、r8、r9、...)和几个专用寄存器(如RFLAGS)。在32位模式下,每个内核有8个256位寄存器,在64位模式下有16个256位寄存器。当AVX-512可用时,将有32个512位寄存器(但在32位模式下只有8个)。

请注意,每个核心的寄存器远远多于您可以直接编程的逻辑寄存器。

见 1.以上

自2006年以来,通过Haswell的Core2处理器每个时钟最多可以处理4µop。然而,使用两种称为微运算融合和宏运算融合的技术,可以使用Haswell实现每个时钟周期六个微运算。

例如,微操作融合可以将负载和加成一个所谓的融合微操作,但每个微操作仍然需要自己的端口。宏观操作融合可以融合,例如标量加法和跳转到一个只需要一个端口的微操作。宏观操作融合本质上是二对一。

Haswell有八个端口。使用这样的七个端口,您可以在一个时钟周期内获得六个微操作。

256-load + 256-FMA    //one fused µop using two ports
256-load + 256-FMA    //one fused µop using two ports
256-store             //one µop using two ports
64-bit add + jump     //one µop using one port

因此,实际上Haswell的每个内核在一个时钟周期内可以处理16个双精度浮点运算(每个FMA处理4个乘法和4个加法)、2个256位加载、1个256位存储和1个64位加法和分支。在这个问题中,获得-peak-bandwidth-on-has-well-in-the-L1-cache-only-getting-62,我使用六个端口在一个时钟周期内获得(理论上)五个微操作。然而,实际上在Haswell上这是很难实现的。

对于读取两个数组并写入一个数组的特定操作,它受每个时钟周期两次读取的约束,因此每个时钟周期只能发出一个FMA。所以它最多只能在每个时钟周期进行四次双精度运算。

但让我告诉你一个小秘密,英特尔不想让人们谈论太多。大多数操作都受内存带宽限制,无法从并行化中受益。这包括您问题中的操作。因此,尽管英特尔每隔几年就推出新技术(例如,AVX、FMA、AVX512、双倍的核心数量),每次都会使性能翻倍,声称摩尔定律在实践中得到了应用,但平均收益是线性的,而不是指数的,而且几年来一直如此。

 类似资料:
  • 问题内容: 有人可以告诉我如何摆脱BeanCreationException吗? 在向Owner.java中添加两个变量之后,我得到了BeanCreationException,如下所示: 我还添加了用于猫和狗的getter和setter方法,以及将猫和狗作为宠物子集填充的方法,如下所示: 当我在Eclipse中在服务器上运行方式为…运行时,应用程序无法初始化,并显示以下错误消息: 这是busin

  • 问题内容: 我正在通过“ Python进行数据分析”,但是我不了解特定的功能。添加两个熊猫系列对象将自动对齐索引数据,但是如果一个对象不包含该索引,则将其作为NaN返回。例如从书中: 结果: 当我将它们加在一起时,我得到了… 那么,为什么犹他州的价值是NaN而不是500?看来500 + NaN = 500。是什么赋予了?我缺少什么,请解释。 更新: 问题答案: 熊猫不假定500 + NaN = 5

  • 问题内容: 我经常对Python列表进行矢量加法。 示例:我有两个这样的列表: 我现在想将b添加到a以获得结果。 通常我最终会这样: 有没有什么有效的,标准的方法可以减少打字? 更新:可以假定列表的长度为3,并且包含浮点数。 问题答案: 我认为您找不到比问题中提出的3个总和更快的解决方案。numpy的优点对于较大的矢量以及在需要其他运算符时都是显而易见的。numpy对于矩阵特别有用,而python

  • 问题内容: 我正在尝试在python中添加两个分数 如果输入1/4 + 1/4,我期望得到1/2结果 我用加法建立了一个分数类 但是我得到的输出是2,4,实际上是1/2,只是没有简化。我该如何解决这个问题? 问题答案: 简化分数的一般方法是找到分子和分母的最大公约数,然后将两者除以

  • 问题内容: 我有两次弦乐时间 有没有简单的方法可以将这两个时间相加并获得一个新的时间 呢? 我想在客户端执行此操作,因此,如果可以避免使用任何日期数据库 问题答案: 请记住,您可以将小时/分钟/秒的整数值转换为单个整数,如下所示: 并转换回: 或者,您可以按以下步骤进行算术零碎:

  • 到目前为止,我有以下代码: 当我运行这个脚本并向它传递一个参数时,我得到一个错误:“./adddir:line 3:[[1:command not found./adddir:line 3:[1:command not found” 当我使用2个参数而不是“1”运行它时,它会显示为“2” 有事吗?