代码之家  ›  专栏  ›  技术社区  ›  Pavel P

在uint8x8\t neon寄存器中查找最小值和最小元素的位置

  •  2
  • Pavel P  · 技术社区  · 6 年前

    考虑以下代码:

    uint8_t v[8] = { ... };
    int ret = 256;
    int ret_pos = -1;
    for (int i=0; i<8; ++i)
    {
        if (v[i] < ret)
        {
            ret = v[i];
            ret_pos = i;
        }
    }
    

    它查找min和min元素的位置( ret ret_pos ).我可以用手臂上的霓虹灯 pairwise min 在v中找到最小元素,但如何找到最小元素的位置?

    更新:看看我自己的答案,你有什么建议来改进它?

    3 回复  |  直到 6 年前
        1
  •  1
  •   gorilon    6 年前

    成对最小值允许您在2个向量之间进行比较,以找到每个对应单词之间的最小值。例如,如果将8个数据点(可能需要更多的矢量化代码)拆分为2个矢量,则可以使用pairwise min来查找4对之间比较的最小值。

    然后,您可以继续将数据拆分为更小的向量对,或者在这个由4个条目组成的新向量上连续迭代以找到最小值。注意找到向量的位置,检查原始向量中的相同位置将得到最小值的位置。或者,也可以使用向量比较来查找此值。

        2
  •  1
  •   Pavel P    6 年前

    在花了一些时间摆弄比特和数学之后,我是这样做的:

    #define VMIN8(x, index, value)                               \
    do {                                                         \
        uint8x8_t m = vpmin_u8(x, x);                            \
        m = vpmin_u8(m, m);                                      \
        m = vpmin_u8(m, m);                                      \
        uint8x8_t r = vceq_u8(x, m);                             \
                                                                 \
        uint8x8_t z = vand_u8(vmask, r);                         \
                                                                 \
        z = vpadd_u8(z, z);                                      \
        z = vpadd_u8(z, z);                                      \
        z = vpadd_u8(z, z);                                      \
                                                                 \
        unsigned u32 = vget_lane_u32(vreinterpret_u32_u8(z), 0); \
        index = __lzcnt(u32);                                    \
        value = vget_lane_u8(m, 0);                              \
    } while (0)
    
    
    uint8_t v[8] = { ... };
    
    static const uint8_t mask[] = { 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01 };
    uint8x8_t vmask = vld1_u8(mask);
    
    uint8x8_t v8 = vld1_u8(v);
    int ret;
    int ret_pos;
    VMIN8(v8, ret_pos, ret);
    

    其中\uu lzcnt是 clz (\uu gcc中的内置clz)。

    下面是它的工作原理。首先,使用成对最小值将uint8x8\u t的所有u8字段设置为最小值:

        uint8x8_t m = vpmin_u8(x, x);
        m = vpmin_u8(m, m);
        m = vpmin_u8(m, m);
    

    然后使用“向量比较”将“最小元素”设置为“所有1”,将所有其他元素设置为“0”:

        uint8x8_t r = vceq_u8(x, m);
    

    然后使用包含值的掩码执行逻辑“与”: uint8_t mask[] {1<<7, 1<<6, 1<<5, ... 1<<1, 1<<0 }; :

    uint8x8_t z = vand_u8(vmask, r);
    

    然后使用成对加法将

    z = vpadd_u8(z, z);
    z = vpadd_u8(z, z);
    z = vpadd_u8(z, z);
    

    然后使用clz计算第一个最小元素的位置。

    unsigned u32 = vget_lane_u32(vreinterpret_u32_u8(z), 0);
    index = __lzcnt(u32);
    

    然后,在实际代码中,我在每个循环迭代和编译器中多次使用VMIN8 is able to perfectly interleave multiple VMIN8 calls 以避免数据暂停。

        3
  •  0
  •   tigertang    3 年前

    vminvq_u8

    矢量上的无符号最小值。此指令比较源SIMD中的所有矢量元素(&A);FP寄存器,并将最小值作为标量写入目标SIMD&FP寄存器。此指令中的所有值都是无符号整数值。