查找具有最大值的元素的INDEX。使用AVX512指令的绝对值

阮氏My Tuyen

我是使用AVX512指令进行编码的新手。我的机器是Intel KNL7250。我试图使用AVX512指令来查找具有最大绝对值的元素的INDEX,该元素的双精度和数组%8 = 0的大小加倍。但是,每次打印输出索引= 0。我不知道哪里有问题,请帮助我。另外,如何将printf用于__m512i类型?

谢谢。

码:

void main()
{
    int i;
    int N=160;
    double vec[N];

    for(i=0;i<N;i++)
    {
        vec[i]=(double)(-i) ;
        if(i==10)
        {
            vec[i] = -1127;
        }
    }

    int max = avxmax_(N, vec);
    printf("maxindex=%d\n", max);
}

int avxmax_(int   N,     double *X )
{
    // return the index of element having maximum absolute value.
    int maxindex, ix, i, M;
    register __m512i increment, indices, maxindices, maxvalues, absmax, max_values_tmp, abs_max_tmp, tmp;
    register __mmask8 gt;
    double values_X[8];
    double indices_X[8];
    double maxvalue;
    maxindex = 1;
    if( N == 1) return(maxindex);


    M = N % 8;
    if( M == 0)
    {
        increment =  _mm512_set1_epi64(8); // [8,8,8,8,8,8,8,8]
        indices = _mm512_setr_epi64(0, 1, 2, 3, 4, 5, 6, 7);
        maxindices = indices;
        maxvalues =  _mm512_loadu_si512(&X[0]);
        absmax = _mm512_abs_epi64(maxvalues); 

        for( i = 8; i < N; i += 8)
        {
            // advance scalar indices: indices[0] + 8, indices[1] + 8,...,indices[7] + 8
            indices = _mm512_add_epi64(indices, increment);

            // compare
            max_values_tmp  = _mm512_loadu_si512(&X[i]);
            abs_max_tmp = _mm512_abs_epi64(max_values_tmp);
            gt           = _mm512_cmpgt_epi64_mask(abs_max_tmp, absmax);

            // update
            maxindices = _mm512_mask_blend_epi64(gt, maxindices, indices);
            absmax     = _mm512_max_epi64(absmax, abs_max_tmp);
        }

        // scalar part
        _mm512_storeu_si512((__m512i*)values_X, absmax);
        _mm512_storeu_si512((__m512i*)indices_X, maxindices);
        maxindex = indices_X[0];
        maxvalue = values_X[0];
        for(i = 1; i < 8; i++)
        {
            if(values_X[i] > maxvalue)
            {
                maxvalue = values_X[i];
                maxindex = indices_X[i];
            }
            
        }
        return(maxindex);
    }
}
彼得·科德斯

函数返回0是因为您将int64索引视为的位模式double,并将该(小)数字转换为整数。 double indices_X[8];是错误;应该是uint64_t还有其他错误,请参见下文。

如果在使用变量时声明了C99样式而不是过时的C89样式,则更容易发现此错误。

_mm512_storeu_si512int64_t索引向量转换为double indices_X[8],将其类型化为两倍,然后以纯C形式进行int maxindex = indices_X[0];这是隐式类型转换,可将该子范式double转换为整数。

(我注意到,在将代码转换为初始化程序旁边的C99样式变量声明时vcvttsd2si,在asm https://godbolt.org/z/zsfc36中进行了神秘的FP-> int转换。这是一个线索:不应有FP-> int我注意到大约在同一时间,我将double indices_X[8];声明下移到使用它的块中,并注意到它具有类型double


实际上可以使用整数运算上FP位模式

但前提是您使用正确的!IEEE754指数偏差表示可以将编码/位模式作为符号/幅度整数进行比较。因此,您可以进行abs / min / max进行比较,但是当然不能进行整数add / sub(除非您正在实现nextafter)。

_mm512_abs_epi64是2的补码绝对值,不是符号幅度相反,您必须仅屏蔽符号位。然后,您都准备将结果视为无符号整数或有符号2的补码。(之所以起作用,是因为高位清除了。)

使用整数最大值具有有趣的性质,即NaNs的比较将高于任何其他值,Inf低于该值,然后是有限值。因此,我们基本上免费获得NaN传播的寻星器。

在KNL(Knight's Landing)上,FPvmaxpdFPvcmppd具有与整数等效值相同的性能:2个周期的延迟,0.5c的吞吐量。https://agner.org/optimize/)。因此,您的方式在KNL上的优势为零,但这对于Skylake-X和IceLake等主流英特尔来说是一个绝妙的窍门。


错误修正的优化版本:

  • 使用size_t用于返回类型和循环计数器/索引来处理潜在的巨大阵列,而不是随机的混合int和64位的向量元素。uint64_t对于收集horizo​​ntal-max的临时数组:即使在具有32位指针/ size_t的构建中,它也始终为64位。)

  • 错误修正:返回0N == 1,而不是1:唯一元素的索引为0。

  • 返程:bug修复-1N%8 != 0的,而不是脱落的非void函数结束。(如果调用者在C中使用结果,或者在C ++中执行一旦结束,则为未定义的行为)。

  • 错误修正:FP值的绝对值=清除符号位,而不是位模式上的2的补码绝对值

  • 错误修复:使用无符号整数compare和max,因此它适用于2的补码整数_mm512_abs_epi64(产生无符号结果;请记住,如果继续将其视为有符号-LONG_MINLONG_MIN则会溢出到)。

  • 改善风格:if (N%8 != 0) return -1;不要将大部分身体放在if块中。

  • 样式改进:首次使用时声明var,并删除了一些纯噪声未使用的变量自20年前标准化C99以来,这对于C来说是惯用的。

  • 样式改进:为tmp向量var使用更简单的名称,它们仅保存加载结果。有时您只需要一个tmp var,因为内在名称很长,以至于您不想_mm...load...为另一个内在输入arg。v作用于内部循环的名称是一个明显的标志,它只是一个占位符,以后不再使用。(此样式在初始化时声明时效果最好,因此很容易看出它不能在外部范围中使用。)

  • 优化:使用SIMD在循环后减少8-> 4个元素:提取上半部分,并与现有的下半部分合并。(与您进行简单的水平缩减(例如sum或max)相同)。当我们需要仅AVX512具有但KNL没有AVX512VL的指令时,这很不方便,因此我们必须使用512位版本,而忽略高垃圾率。但是KNL确实有AVX1 / AVX2,因此我们仍然可以存储256位向量并做一些事情。

    使用合并屏蔽_mm512_mask_extracti64x4_epi64提取来直接混合同一矢量的下半部分是一个很酷的技巧,如果您使用512位掩码混合,编译器将找不到这些技巧。:P

  • 某种错误修复:在C中,托管实现main的返回类型为int(在OS下运行)。

#include <immintrin.h>
#include <stdio.h> 
#include <stdint.h>
#include <stdlib.h>

// bugfix: indices can be larger than an int
size_t avxmax_(size_t N,  double *X )
{
    // return the index of element having maximum absolute value.
    if( N == 1)
        return 0;    // bugfix: 0 is the only valid element in this case, not 1
    if( N % 8 != 0)      // [[unlikely]] // C++20
        return -1;   // bugfix: don't fall off the end of the function in this case

    const __m512i fp_absmask = _mm512_set1_epi64(0x7FFFFFFFFFFFFFFF);
    __m512i indices = _mm512_setr_epi64(0, 1, 2, 3, 4, 5, 6, 7);
    __m512i maxindices = indices;
    __m512i v =  _mm512_loadu_si512(&X[0]);
    __m512i absmax = _mm512_and_si512(v, fp_absmask);
    for(size_t i = 8; i < N; i += 8) // [[likely]]  // C++20
    {
        // advance indices by 8 each.
        indices = _mm512_add_epi64(indices, _mm512_set1_epi64(8));
        // compare
                v    = _mm512_loadu_si512(&X[i]);
        __m512i vabs = _mm512_and_si512(v, fp_absmask);
             // vabs = _mm512_abs_epi64(max_values_tmp);  // for actual integers, not FP bit patterns
        __mmask8 gt  = _mm512_cmpgt_epu64_mask(vabs, absmax);
        // update
        maxindices = _mm512_mask_blend_epi64(gt, maxindices, indices);
        absmax     = _mm512_max_epu64(absmax, vabs);
    }

    // reduce 512->256; KNL doesn't have AVX512VL so some ops require 512-bit vectors
    __m256i absmax_hi = _mm512_extracti64x4_epi64(absmax, 1);
    __m512i absmax_hi512 = _mm512_castsi256_si512(absmax_hi); // free
    __mmask8 gt = _mm512_cmpgt_epu64_mask(absmax_hi512, absmax);
    __m256i abs256 = _mm512_castsi512_si256(_mm512_max_epu64(absmax_hi512, absmax));  // reduced to low 4 elements

    // extract with merge-masking = blend
    __m256i maxindices256 = _mm512_mask_extracti64x4_epi64(
                           _mm512_castsi512_si256(maxindices), gt, maxindices, 1);

    // scalar part
    double values_X[4];
    uint64_t indices_X[4];
    _mm256_storeu_si256((__m256i*)values_X, abs256);
    _mm256_storeu_si256((__m256i*)indices_X, maxindices256);

    size_t maxindex = indices_X[0];
    double  maxvalue = values_X[0];
    for(int i = 1; i < 4; i++)
    {
        if(values_X[i] > maxvalue)
        {
            maxvalue = values_X[i];
            maxindex = indices_X[i];
        }
        
    }
    return maxindex;
}

在Godbolt上:GCC10.2 -O3 -march = knl的主循环是8条指令。因此,即使(最好的情况)KNL可以以2 / clock的频率解码并运行它,每个向量仍然要花费4个周期。您可以在Godbolt上运行该程序;它在Skylake-X服务器上运行,因此可以运行AVX512代码。您可以看到它打印出来10

.L4:
        vpandd  zmm2, zmm5, ZMMWORD PTR [rsi+rax*8]   # load, folded into AND
        add     rax, 8
        vpcmpuq k1, zmm2, zmm0, 6
        vpaddq  zmm1, zmm1, zmm4                    # increment current indices
        cmp     rdi, rax
        vmovdqa64       zmm3{k1}, zmm1              # blend maxidx using merge-masking
        vpmaxuq zmm0, zmm0, zmm2
        ja      .L4

        vmovapd zmm1, zmm3                        # silly missed optimization related to the case where the loop runs 0 times.
.L3:
        vextracti64x4   ymm2, zmm0, 0x1           # high half of absmax
        vpcmpuq k1, zmm2, zmm0, 6                 # compare high and low
        vpmaxuq zmm0, zmm0, zmm2
      #  vunpckhpd       xmm2, xmm0, xmm0  # setting up for unrolled scalar loop
        vextracti64x4   ymm1{k1}, zmm3, 0x1       # masked extract of indices

循环的另一个选项是masked vpbroadcastq zmm3{k1}, rax[0..7]在循环之后添加每个元素的偏移量。这实际上将节省vpaddq循环,并且i如果GCC仍将使用索引寻址模式,则我们拥有寄存器的权利(在Skylake-X上不好;击败了内存源的微融合vpandd。)

Agner Fog没有列出GP-> vector广播的性能,但希望它至少在KNL上只有单播。(并且https://uops.info/没有KNL或KNM结果)。


分支策略:很少有新的最大值

如果您期望找到一个新的最大值非常罕见(例如,数组很大且分布均匀,或者至少没有向上趋势),则广播当前最大值并在找到任何更大的向量元素时分支可能会更快。

找到一个新的max意味着跳出循环(可能会预测错误,因此很慢)并广播该元素(可能使用atzcnt查找元素索引,然后广播加载并更新索引)。

特别是使用KNL的4路SMT来隐藏分支未命中成本时,对于大型阵列而言,这可能是整体吞吐量的胜利;平均每个元素更少的指令。

但对于确实呈上升趋势的输入,可能会变得更糟,因此,平均发现新的最大值是O(n)倍,而不是sqrt(n)或log(n)或均匀分布给我们的频率。


PS:打印矢量,存储到数组中并重新加载元素。打印__m128i变量

或使用调试器向您展示其元素。

本文收集自互联网,转载请注明来源。

如有侵权,请联系[email protected] 删除。

编辑于
0

我来说两句

0条评论
登录后参与评论

相关文章

来自分类Dev

确定包含具有交替符号的元素的子列表,其元素的绝对值之和最大

来自分类Dev

使用TCL查找字典中值的最大绝对值

来自分类Dev

从std :: map查找具有最大值的元素

来自分类Dev

在python中递归查找具有最大值的元素

来自分类Dev

查找数组中行的绝对值,然后打印出答案的最大值

来自分类Dev

Ruby使用max_by在多维数组中查找具有最大值的元素

来自分类Dev

使用if语句查找绝对值

来自分类Dev

查找许多列的最大绝对值的有效方法

来自分类Dev

Excel中是否有函数来查找范围的最大绝对值?

来自分类Dev

Java Stream:查找具有属性的最小/最大值的元素

来自分类Dev

查找具有多个匹配条件的最接近的数字索引+匹配+最小+绝对值

来自分类Dev

熊猫数据框通过绝对值优化获得最大值和最小值

来自分类Dev

查找具有公共密钥的哈希数组的最大值?

来自分类Dev

NetLogo-查找具有最大值的补丁

来自分类Dev

在列表中查找具有最大值的项目

来自分类Dev

查找组中具有最大值的行

来自分类Dev

查找具有最大值的父对象

来自分类Dev

在键值对的 PCollection 中查找具有最大值的键

来自分类Dev

通过单元格绝对值的最大值生成的宏

来自分类Dev

从Angular中的对象获取具有最大值的元素

来自分类Dev

MongooseJS-如何找到具有最大值的元素?

来自分类Dev

寻找具有最大值属性的元素更快

来自分类Dev

具有绝对值的大O标记?

来自分类Dev

在具有位置:绝对值的Div末尾添加页脚

来自分类Dev

Django 中具有绝对值的数值运算

来自分类Dev

如何选择具有相同数字绝对值的行

来自分类Dev

使用ARM NEON指令查找数组的最小值和最大值

来自分类Dev

在Javascript数组中查找绝对最大值

来自分类Dev

如何更改任一指令的所有指令作用域值?

Related 相关文章

  1. 1

    确定包含具有交替符号的元素的子列表,其元素的绝对值之和最大

  2. 2

    使用TCL查找字典中值的最大绝对值

  3. 3

    从std :: map查找具有最大值的元素

  4. 4

    在python中递归查找具有最大值的元素

  5. 5

    查找数组中行的绝对值,然后打印出答案的最大值

  6. 6

    Ruby使用max_by在多维数组中查找具有最大值的元素

  7. 7

    使用if语句查找绝对值

  8. 8

    查找许多列的最大绝对值的有效方法

  9. 9

    Excel中是否有函数来查找范围的最大绝对值?

  10. 10

    Java Stream:查找具有属性的最小/最大值的元素

  11. 11

    查找具有多个匹配条件的最接近的数字索引+匹配+最小+绝对值

  12. 12

    熊猫数据框通过绝对值优化获得最大值和最小值

  13. 13

    查找具有公共密钥的哈希数组的最大值?

  14. 14

    NetLogo-查找具有最大值的补丁

  15. 15

    在列表中查找具有最大值的项目

  16. 16

    查找组中具有最大值的行

  17. 17

    查找具有最大值的父对象

  18. 18

    在键值对的 PCollection 中查找具有最大值的键

  19. 19

    通过单元格绝对值的最大值生成的宏

  20. 20

    从Angular中的对象获取具有最大值的元素

  21. 21

    MongooseJS-如何找到具有最大值的元素?

  22. 22

    寻找具有最大值属性的元素更快

  23. 23

    具有绝对值的大O标记?

  24. 24

    在具有位置:绝对值的Div末尾添加页脚

  25. 25

    Django 中具有绝对值的数值运算

  26. 26

    如何选择具有相同数字绝对值的行

  27. 27

    使用ARM NEON指令查找数组的最小值和最大值

  28. 28

    在Javascript数组中查找绝对最大值

  29. 29

    如何更改任一指令的所有指令作用域值?

热门标签

归档