我有一个12位整数的大型紧密封装数组,采用以下重复位封装模式:(其中An/Bn 中的 n 表示位数,A和B是数组中的前两个12位整数)
| byte0 | byte1 | byte2 | etc..
| A11 A10 A9 A8 A7 A6 A5 A4 | B11 B10 B9 B8 B7 B6 B5 B4 | B3 B2 B1 B0 A3 A2 A1 A0 | etc..
字符串
我将其重新排序为以下模式:
| byte0 | byte1 | byte2 | etc..
| A11 A10 A9 A8 A7 A6 A5 A4 | A3 A2 A1 A0 B11 B10 B9 B8 | B7 B6 B5 B4 B3 B2 B1 B0 | etc..
型
我已经让它在每3字节循环中工作,代码如下:
void CSI2toBE12(uint8_t* pCSI2, uint8_t* pBE, uint8_t* pCSI2LineEnd)
{
while (pCSI2 < pCSI2LineEnd) {
pBE[0] = pCSI2[0];
pBE[1] = ((pCSI2[2] & 0xf) << 4) | (pCSI2[1] >> 4);
pBE[2] = ((pCSI2[1] & 0xf) << 4) | (pCSI2[2] >> 4);
// Go to next 12-bit pixel pair (3 bytes)
pCSI2 += 3;
pBE += 3;
}
}
型
但是使用字节粒度对性能不是很好。目标CPU是64位ARM Cortex-A72(树莓派计算模块4)。对于上下文,此代码将MIPI CSI-2位压缩的原始图像数据转换为Adobe DNG的位压缩。
我希望使用SIMD intrinsic可以获得相当大的性能改进,但我真的不知道从哪里开始。我有SIMDe头来翻译内部函数,所以AVX/AVX 2解决方案是受欢迎的。
2条答案
按热度按时间kd3sttzy1#
neon
ld3
指令是理想的;它加载48个字节并将它们解压缩到三个 neon 寄存器中。那你只需要轮班和手术室我得出了以下结论:
字符串
Try on godbolt的数据。
使用gcc,生成的程序集看起来与您所期望的一样。(有一个
mov
可以通过更好的寄存器分配来消除,但这很小。不幸的是,clang有一个奇怪的优化缺失,它将4位右移分解为3位和1位的移位。I filed a bug。
原则上,我们可以使用
sli
,Shift Left和Insert来做得更好,以有效地将OR与其中一个移位合并:型
但是因为它覆盖了源操作数,所以我们需要额外增加几个
mov
。clang更巧妙地分配寄存器,只需要一个mov
。另一个可能稍微快一点的选项是使用
sra
、Shift Right和Accumulate,它执行加法而不是插入。由于相关位在这里已经是零,所以这具有相同的效果。奇怪的是没有sla
。型
hgc7kmma2#
我建议你从图表开始。
我不能说 neon ,所以我将描述我将如何使AVX 2代码做你想要的(但是,你应该用你的目标指令集实现它;如果你的目标是编写新代码,最好不要使用转换器)。x64 intrinsic有很好的文档; here is an example我用的。
AVX 2寄存器具有256位或32字节。也就是10个单位的24位数据。画一张图(最好是在纸上):画出如果从内存中读取,256位寄存器将包含哪些位。然后画出你想在变形后得到的比特。用线连接它们。识别具有相同相对位置的位块。
然后编写代码,隔离相关的位块(
_mm256_and_si256
),将它们移位(_mm256_slli_si256
,可能是_mm256_bslli_epi128
或其他)并组合它们(_mm256_or_si256
)。AVX 2在移位方面特别特殊,所以我相信 neon 代码会更容易编写。你的主循环应该包含阅读、处理和写入3个寄存器,或者768位。如果你只为第一个做了一个图,你也许可以类似地实现其他两个。当然,您需要对循环剩余部分(最后几个数据元素)进行特殊处理-使用常规C代码处理它们。