出于学术目的,我想尝试为以下算法编写一个 ARM NEON 优化,即使只是为了测试是否有可能获得任何性能改进。我认为这不是 SIMD 优化的好选择,因为结果被合并在一起,失去了并行化收益。
这是算法:
const uchar* center = ...;
int t0, t1, val;
t0 = center[0]; t1 = center[1];
val = t0 < t1;
t0 = center[2]; t1 = center[3];
val |= (t0 < t1) << 1;
t0 = center[4]; t1 = center[5];
val |= (t0 < t1) << 2;
t0 = center[6]; t1 = center[7];
val |= (t0 < t1) << 3;
t0 = center[8]; t1 = center[9];
val |= (t0 < t1) << 4;
t0 = center[10]; t1 = center[11];
val |= (t0 < t1) << 5;
t0 = center[12]; t1 = center[13];
val |= (t0 < t1) << 6;
t0 = center[14]; t1 = center[15];
val |= (t0 < t1) << 7;
d[i] = (uchar)val;
这就是我在 ARM 汇编中的想法:
VLD2.8 {d0, d1} ["center" addr]
假设 8 位字符,第一个操作应该将所有 t0 和 t1 值交替加载到 2 个寄存器中。
VCLT.U8 d2, d0, d1
所有比较的“小于”的单个操作。注意:我读过 VCLT 只能使用 #0 常量作为第二个操作数,因此必须在 >= 中反转。阅读 ARM 文档,我认为每个 8 位值的结果将是“全 1”表示真(11111111)或“全 0”表示假(00000000)。
VSHR.U8 d4, d2, #7
此右移将删除寄存器 8 位“单元”中的 8 个值中的 7 个(主要是删除 7 个)。我使用了 d4,因为下一步将是在 q2 中映射的第一个 d 寄存器。
现在问题开始了:移位和 OR。
VSHLL.U8 q2[1], d4[1], 1
VSHLL.U8 q2[2], d4[2], 2
...
VSHLL.U8 q2[7], d4[7], 7
我只能以这种方式想象(如果可以使用 [offsets])进行左移。根据文档,应指定 Q2 而不是 d4。
VORR(.U8) d4[0], d4[1], d4[0]
VORR(.U8) d4[0], d4[2], d4[0]
...
VORR(.U8) d4[0], d4[7], d4[0]
最后一步应该给出结果。
VST1.8 d4[0], [d[i] addr]
结果的简单存储。
这是我第一次使用 ARM NEON,所以可能很多假设可能是不正确的。帮助我了解可能的错误,并在可能的情况下提出更好的解决方案。
编辑: 这是建议的解决方案之后的最终工作代码:
__asm__ __volatile ("VLD2.8 {d0, d1}, [%[ordered_center]] \n\t"
"VCGT.U8 d2, d1, d0 \n\t"
"MOV r1, 0x01 \n\t"
"MOV r2, 0x0200 \n\t"
"ORR r2, r2, r1 \n\t"
"MOV r1, 0x10 \n\t"
"MOV r3, 0x2000 \n\t"
"ORR r3, r3, r1 \n\t"
"MOVT r2, 0x0804 \n\t"
"MOVT r3, 0x8040 \n\t"
"VMOV.32 d3[0], r2 \n\t"
"VMOV.32 d3[1], r3 \n\t"
"VAND d0, d2, d3 \n\t"
"VPADDL.U8 d0, d0 \n\t"
"VPADDL.U16 d0, d0 \n\t"
"VPADDL.U32 d0, d0 \n\t"
"VST1.8 d0[0], [%[desc]] \n\t"
:
: [ordered_center] "r" (ordered_center), [desc] "r" (&desc[i])
: "d0", "d1", "d2", "d3", "r1", "r2", "r3");