有效使用vmlaq_s16
Posted
技术标签:
【中文标题】有效使用vmlaq_s16【英文标题】:Effective use of vmlaq_s16 【发布时间】:2014-07-15 18:04:34 【问题描述】:当使用 vmlaq_s16 intrinsic/VMLA.I16 指令时,结果采用一组 8 个 16 位 整数的形式。然而,指令中的乘法需要将结果存储在 32 位 整数中以防止溢出。
在具有 SSE2 的 Intel 处理器上,_mm_madd_epi16 通过将向量的连续元素对相乘和相加来保留指令的长度(8 个 16 位整数转换为 4 个 32 位结果),即
r0 := (a0 * b0) + (a1 * b1)
r1 := (a2 * b2) + (a3 * b3)
r2 := (a4 * b4) + (a5 * b5)
r3 := (a6 * b6) + (a7 * b7)
其中 r0,r1,r2,r3 都是 32 位元素,a0-a7, b0-b7 都是 16 位元素。
vmlaq_s16 指令中是否有一个技巧让我仍然能够同时处理 8 个 16 位元素并且结果不会溢出?还是说这条指令只是为固有在 4 位范围内的操作数提供的(非常值得怀疑)?
谢谢!
编辑:所以我只是想到如果 vmlaq_s16 为结果中的每个元素设置溢出寄存器标志,那么很容易计算溢出并恢复结果。
编辑 2:供大家参考,这里是如何加载 8 个元素并在 128 位寄存器上使用内部函数流水线化两个长乘加(使用 VS2012 为 ARM 目标编译的概念验证代码) :
signed short vector1[] = 1, 2, 3, 4, 5, 6, 7, 8;
signed short vector2[] = 1, 2, 3, 4, 5, 6, 7, 8;
int16x8_t v1; // = vdupq_n_s16(0);
int16x8_t v2; // = vdupq_n_s16(0);
v1 = vld1q_s16(vector1);
v2 = vld1q_s16(vector2);
int32x4_t sum = vdupq_n_s16(0);
sum = vmlal_s16(sum, v1.s.low64, v2.s.low64);
sum = vmlal_s16(sum, v1.s.high64, v2.s.high64);
printf("sum: %d\n", sum.n128_i32[0]);
【问题讨论】:
遗憾的是,NEON 没有标志寄存器的概念——你得到的最接近的是未捕获的浮点异常状态位,这在这里根本没有帮助:( 【参考方案1】:这些不是直接等效的操作 - VMLA
将两个向量相乘,然后将结果逐元素添加到 第三个向量,这与英特尔 @ 的自包含的半元素半水平疯狂不同987654322@。由于第 3 个向量是常规操作数,它必须存在于寄存器中,因此 256 位累加器没有空间。
如果您不想通过使用VMLA
执行 8x16 * 8x16 + 8x16 来冒溢出的风险,另一种方法是使用 VMLAL
执行 4x16 * 4x16 + 4x32。显而易见的建议是将指令对流水线化,将 8x16 向量处理成两个 4x32 累加器,然后在最后将它们相加,但我承认我对内在函数不太熟悉,所以我不知道它们会有多困难(与汇编相比,您可以利用“64 位向量”和“128 位向量”只是同一寄存器文件的可互换视图这一事实)。
【讨论】:
谢谢。这就是我目前正在做的事情 - vmlal_s16,它只使用了“可用”带宽的一半,这让我很烦恼。因此,流水线指令对的明显建议的优点是它节省了另一个负载,对吗?也就是说,我将在块的开头进行一次 16 字节的加载,而不是进行两次 8 字节的加载,这样可以节省一次内存和缓存。 确实 - 如果数据已经在寄存器中,每个额外的VMLAL
只需要多花费 1 个周期(在最初的 ~5-10 个周期结果延迟之上),我相信 i> 某些内核实际上可以让您使用相同的累加器寄存器流水线化多条指令,而不会产生任何额外的损失。据我了解 NEON,仔细预加载和保持 L1 满载比其他任何事情都重要 - 与加载/存储瓶颈相比,数据处理“带宽”几乎是免费的。
这是个好主意,但它只将我的代码速度提高了大约 10%。我将更多地研究程序集,看看我是否可以比 MSVS ARM 编译器更好地将内存负载与 vmal 指令交错。以上是关于有效使用vmlaq_s16的主要内容,如果未能解决你的问题,请参考以下文章
将 WSP 从 SP2013 迁移到 SP2019 - 使用电源外壳,但由于“16”不是有效版本而出现错误