在 uint8x8_t 霓虹灯寄存器中找到最小值和最小元素的位置
Find min and position of the min element in uint8x8_t neon register
考虑这段代码:
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;
}
}
它找到最小元素的最小值和位置(ret
和 ret_pos
)。在 arm neon 中,我可以使用 pairwise min 在 v 中找到最小元素,但是如何找到最小元素的位置?
更新:看我自己的回答,你有什么改进建议?
Pairwise min 允许您比较 2 个向量,找到每个对应单词之间的最小值。例如,如果您的 8 个数据点(可能需要更多的矢量化代码)被分成 2 个向量,您可以使用 pairwise min 来找到 4 对之间比较的最小值。
然后,您可以继续将数据拆分为更小的向量对,或者连续迭代这个包含 4 个条目的新向量以找到最小值。记下找到向量的位置,检查原始向量中的相同位置将得出最小值的位置。或者,您也可以使用向量比较来查找此值。
这是我花了一些时间摆弄位和数学之后的结果:
#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);
其中 __lzcnt 是 clz(gcc 中的 __builtin_clz)。
这是它的工作原理。首先使用 pairwise min 将 uint8x8_t 的所有 u8 字段设置为最小值:
uint8x8_t m = vpmin_u8(x, x);
m = vpmin_u8(m, m);
m = vpmin_u8(m, m);
然后使用向量比较将最小元素设置为所有 1,并将所有其他设置为零:
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);
然后使用成对相加添加
的所有 8 个字节
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 以避免数据停顿。
vminvq_u8
矢量上的无符号最小值。该指令比较源 SIMD&FP 寄存器中的所有向量元素,并将最小值作为标量写入目标 SIMD&FP 寄存器。此指令中的所有值都是无符号整数值。
考虑这段代码:
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;
}
}
它找到最小元素的最小值和位置(ret
和 ret_pos
)。在 arm neon 中,我可以使用 pairwise min 在 v 中找到最小元素,但是如何找到最小元素的位置?
更新:看我自己的回答,你有什么改进建议?
Pairwise min 允许您比较 2 个向量,找到每个对应单词之间的最小值。例如,如果您的 8 个数据点(可能需要更多的矢量化代码)被分成 2 个向量,您可以使用 pairwise min 来找到 4 对之间比较的最小值。
然后,您可以继续将数据拆分为更小的向量对,或者连续迭代这个包含 4 个条目的新向量以找到最小值。记下找到向量的位置,检查原始向量中的相同位置将得出最小值的位置。或者,您也可以使用向量比较来查找此值。
这是我花了一些时间摆弄位和数学之后的结果:
#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);
其中 __lzcnt 是 clz(gcc 中的 __builtin_clz)。
这是它的工作原理。首先使用 pairwise min 将 uint8x8_t 的所有 u8 字段设置为最小值:
uint8x8_t m = vpmin_u8(x, x);
m = vpmin_u8(m, m);
m = vpmin_u8(m, m);
然后使用向量比较将最小元素设置为所有 1,并将所有其他设置为零:
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);
然后使用成对相加添加
的所有 8 个字节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 以避免数据停顿。
vminvq_u8
矢量上的无符号最小值。该指令比较源 SIMD&FP 寄存器中的所有向量元素,并将最小值作为标量写入目标 SIMD&FP 寄存器。此指令中的所有值都是无符号整数值。