zoukankan      html  css  js  c++  java
  • 【翻译】 OPT:SIMD: Achieving Greater Parallelism with SIMD Instructions

    1.Introduction

      正如CSAPP3e3.1节描述的那样,Intel公司在1999年引入SSE指令,而SSE指令就是“Streaming SIMD extensions”的缩写。相反,SIMD则是single-instruction,multiple-data的缩写(这个概念可以从Flynn 分类法得知)。最近x86-64处理器实现了AVX(advanced vector extensions) multimedia instruction。除了提供SSE指令的超集外,AVX还扩展了向量寄存器的大小。我们的PPT是基于AVX2,即第二代AVX(由2013年Core i7引入)。当在命令行中使用参数-mavx2时,GCC将生成AVX2的代码。我们将展示如何使用SIMD指令实现向量计算,其中使用单个操作计算多个数据值。

      AVX2中SIMD执行模型背后的idea是每个32字节YMM寄存器可以保存多个值。在我们的示例中,我们将考虑它们可以包含八个整数或单个精度值或四个双精度值的情况。AVX2指令可以对这些寄存器执行向量运算,例如并行添加或乘以八组或四组值。例如,YMM寄存器%ymm0包括8个单精度浮点数,我们将其表示为a0,a1,…,a7,%rcx包含8个单精度浮点数序列的内存地址,我们将其表示为b0,b1,…,b7。然后指令将从内存中读取8个值,从AVX寄存%ymm0中读取8个值。他将并行执行8个惩罚,计算cißai*bi(-1<i<8)并将这些结果存储在AVX寄存器%ymm1中。我们看到这些单指令能够在多个数据值上生成计算,因此称为SIMD。这个乘法是我们将其称为向量代码,而我们将只能在一个值上运行的代码称为标量代码。

           GCC支持C语言的扩展,让程序员根据向量操作表达程序,可以编译成AVX2的SIMD执行[1]。这种代码风格比直接使用汇编语言写代码更好,因为GCC还可以为其他处理器上的 SIMD指令生成代码。用C语言编写还有一个优点,即GCC将为不支持向量指令的机器生成标量代码。我们将使用我们的组合函数作为示例,描述如何使用GCC编支持写向量操的代码。基本策略是定义一个矢量数据类型vec_t,他包含8个4字节值或4个8字节值,如果我们有两个这样的向量va和vb,则表达式va*vb为向量元素的SIMD乘法。

    2. Declaration

      第一步,我们声明矢量的数据类型,由于我们试图使用相同的代码适用于基本数据类型int, long, float 和 double,因此我们将使用typedef声明和常量定义的组合来使代码更通用。和我们的早期版本一样,我们假设基本数据类型已经声明为data_t 类型。

           我们定义VBYTES为向量中的字节数。对于AVX2,这被定义为32.但我们希望保持次值参数化以便轻松地调整其他机器的代码。然后我们将VSIZE定义为每个vector中的元素数量。

    /* Number of bytes in a vector */
    
    #define VBYTES 32
    
     
    
    /* Number of elements in a vector */
    
    #define VSIZE VBYTES/sizeof(data_t)

           现在我们准备定义矢量数据类型。这涉及到GCC特有的符号: 

    /* Vector data type */
    
    typedef data_t vec_t __attribute__((vector_size(VBYTES)));

    __attirbute__是GNU C特有机制,其可以设置函数属性。上面的声明定义vec_t数据类型中的元素的类型为data_t 并且vec_t的变量尽可能分配空间是采用  VBYTES对齐。 

           可以使用数组表示法引用向量的元素。例如,以下代码将vector元素初始化为组合代码的identity元素:

    /* Declare vector and initialize elements to IDENT*/
    
    vec_t accum;
    
     
    
    for (i=0; i<VSIZE; i++)
    
      accum[i] = IDENT;

           以下显示了计算两个SIMD向量的内积的简单示例: 

    /* Compute inner product product of SSE vector */
    
    data_t innerv(vec_t av, vec_t bv){
    
      long int i;
    
      vec_pv = av*bv;
    
      data_t result = 0;
    
      for (i=0; i<VSIZE; i++)
    
        result += pv[i];
    
      return result;
    
    }

    在此代码中,第4行的乘法运算将向量av和bv的相应元素相乘。第7行的代码访问产品向量的元素,整个循环计算元素的总和。 

    3 Alignment Requirement

    一些AVX2指令对存储器操作数施加了非常严格的对齐要求。他们要求任何数据从存储器读入YMM寄存器,或从YMM寄存器写入存储器,满足32字节对齐。尝试读取或写入未对齐数据的指令可能导致分段错误,指示无效的内存引用。这种对齐要求将影响我们编写使用AVX2指令的程序的方式。(AVX2指令可以访问未对齐的数据,但早期的实现效率不高,因此GCC目前不会生成使用他们的代码。)

    4 Implementation of Combining Function

           图1 展示了使用SIMD操作的组合函数代码,总体思路是设置一个向量变量accum,它并行累加8个(数据类型int和float)或4个(数据类型double)值。

    图1 使用SIMD操作组合函数

           代码首先将累加器初始化为identity元素(第10-11行),使用数组索引来设置向量的各个元素

           为了满足对齐要求,我们可能需要使用标量运算累积几个向量元素,直到剩余的数据向量数据具有VBYTES的倍数的地址。代码见第14-17行。观察我们将指针数据转换为数据类型大小为t,以便我们可以测试它是否是VBYTES的倍数。我们还必须跟踪剩余元素cnt的数量,并考虑cnt小于单个向量中元素数量的情况。

           第20-25行显示了该函数的主循环。我们在这里看到使用强制转换来创建一个指向向量的指针,该向量具有与指向数据的指针相同的地址。取消引用此指针然后从内存中检索整个数据向量,此处由向量变量块定义。语句accum = accum OP chunk然后将从存储器读取的向量值与并行累加器中的值组合。

           如果主循环在所有值累积之前终止,我们有另一个循环来单步执行剩余的元素(第28-31行)。

           然后,我们使用数组下标引用各个累加器元素,并将它们组合起来以确定最终结果(第34-35行)。

    5 Analysis

      下表显示了我们对图1代码的结果,与仅使用标量运算的最佳方法进行了比较:

      请注意,我们现在分别列出不同整数大小(int和long)和不同浮点大小(float和double)的性能。虽然这两种情况对于标量代码具有相同的性能,但它们与矢量代码具有不同的性能,因为一种实现了8路并行,而另一种只实现了4路。我们看到由此产生的性能有点混乱。对于整数加法以及单精度加法和乘法,我们已经打破了我们在标量实现中看到的吞吐量障碍。另一方面,其他操作的表现并不像我们使用标量代码那样好。

      幸运的是,我们可以结合我们早期的技术,通过扩展累加器的数量(参见问题1)或使用重新关联(参见问题2)来进一步增强并行性,从而产生以下性能:

      我们可以看到,矢量代码在四个32位情况下几乎实现了八倍的改进,在四个64位情况中的三个中实现了四倍的改进。当我们尝试在向量代码中表达时,只有长整数乘法代码表现不佳。 AVX指令集不包括一个64位整数的并行乘法,因此GCC不能为这种情况生成矢量代码。使用向量指令为组合操作创建新的吞吐量限制。对于32位操作,这些值比标量限制低8倍,64位操作则低4倍。我们的代码接近于实现数据类型和操作的几种组合的这些边界。

    6 Problems

    Practice Problem 1:

             为组合函数写入向量代码,该函数维护四组不同的累加器,每个累加器累加8个或4个值,具体取决于数据类型。

    Practice Problem 2:

    编写组合函数的向量代码,在每次迭代时从内存中读取四个32字节块,然后使用重新关联来增加可以并行执行的组合操作的数量。

    Practice Problem 3:

    编写CS:APP3e问题5.13中描述的内积计算的SIMD版本,使用单个向量变量并行累加多个和。您不能假设参数向量满足32字节对齐。但是,您可以假设两者的任何程度的错位都是相同的。换句话说,对于指针p,当p是udata时,表达式((long)p)%32将产生与p是vdata时相同的值。

    我们对此功能的实现实现了单精度数据的CPE为0.38,双精度数据的CPE为0.75。

    Practice Problem 4:

    扩展问题3的代码以在四个向量中累加和。我们对此功能的实现对于单精度数据实现了大约0.14的CPE,对于双精度数据实现了0.29,几乎达到了由添加单元施加的吞吐量限制。

    Practice Problem 5:

    编写CS:APP3e问题5.5中描述的多项式评估的SIMD版本,使用单个矢量变量并行累加多个和。无论参数a的对齐如何,您的代码都必须正常工作。我们对此函数的实现实现了1.44的CPE,超过了最佳标量实现的性能。

    Practice Problem 6:

    使用您学到的各种技巧:矢量代码,多个累加器,重新关联,以及Horner的方法来编写最快的多项式求值函数。我们的代码达到了0.35的CPE。

     

     

    答案在pdf最后:http://csapp.cs.cmu.edu/3e/waside/waside-simd.pdf

  • 相关阅读:
    html不点击提交,自动post
    wpf slider刻度
    visual studio 的 code snippet(代码片段)
    更换手机号之前
    post提交
    动态修改settings
    获取文件数据流+叠加byte数组(给byte数组加包头包尾)
    装箱与拆箱
    ue4中窗口打开web地址
    C++通过Callback向C#传递数据,注意问题
  • 原文地址:https://www.cnblogs.com/SsoZhNO-1/p/11060637.html
Copyright © 2011-2022 走看看