使用内在函数进行Neon优化
|
了解ARM NEON内部函数后,我正在计时编写的函数以使数组中的元素加倍。使用内部函数的版本比该函数的普通C版本花费更多的时间。
没有NEON:
void double_elements(unsigned int *ptr, unsigned int size)
{
unsigned int loop;
for( loop= 0; loop<size; loop++)
ptr[loop]<<=1;
return;
}
使用NEON:
void double_elements(unsigned int *ptr, unsigned int size)
{
unsigned int i;
uint32x4_t Q0,vector128Output;
for( i=0;i<(SIZE/4);i++)
{
Q0=vld1q_u32(ptr);
Q0=vaddq_u32(Q0,Q0);
vst1q_u32(ptr,Q0);
ptr+=4;
}
return;
}
想知道数组和向量之间的加载/存储操作是否正在消耗更多时间,这抵消了并行加法的好处。
更新:更多信息响应Igor的回复。
1.代码发布在这里:
普通的
平原
霓虹灯
霓虹灯
从两个汇编清单的L7部分中,我看到霓虹灯版本的汇编指令数量更多(因此需要更多时间吗?)
2.我在arm-gcc上使用-mfpu = neon进行编译,没有其他标志或优化。对于普通版本,根本没有编译器标志。
3.那是一个错字,SIZE就是大小;两者都是一样的。
4,5。尝试了4000个元素的数组。我在函数调用前后使用gettimeofday()进行计时。NEON= 230us,ordinary = 155us。
6.是的,我在每种情况下都打印了元素。
7.Did,没有任何改善。
没有找到相关结果
已邀请:
3 个回复
诧不达
,第二个函数使用
,这是故意的吗?他们是一样的吗? 您尝试过的阵列大小是多少?我不希望NEON在两个方面都提供帮助。 速度差是多少?几个百分点?几个数量级? 您是否检查过结果是否相同?您确定代码是等效的吗? 您正在为中间结果使用相同的变量。尝试将加法结果存储在另一个变量中,这可能会有所帮助(尽管我希望编译器会很聪明,并分配一个不同的寄存器)。另外,您可以尝试使用shift(
)代替加法。 编辑:感谢您的答案。我四处张望,发现了这个讨论,说(强调我的): 将数据从NEON移至ARM寄存器 是Cortex-A8很贵,所以NEON在 Cortex-A8最适合用于大型 小ARM工作块 管道交互。 在您的情况下,没有NEON到ARM的转换,而只能加载和存储。尽管如此,非NEON部件似乎吞噬了并行操作的节省。我希望在NEON中可以完成许多工作的代码中获得更好的结果,例如颜色转换。
递劝臼类洪
原始代码存在一些问题(优化程序可能会解决一些问题,而其他一些可能无法解决,您需要在生成的代码中进行验证): 添加的结果仅在NEON管道的N3阶段可用,因此以下存储将停止。 假设编译器未展开循环,则循环/分支可能会产生一些开销。 它没有利用通过另一个NEON指令双重发布加载/存储的功能。 如果源数据不在缓存中,则负载将停止。您可以使用__builtin_prefetch内在函数预加载数据以加快此速度。 另外,正如其他人指出的,该操作相当琐碎,对于更复杂的操作,您会看到更多收益。 如果要使用内联汇编编写此代码,则还可以: 使用对齐的加载/存储(我认为内部函数无法生成),并确保您的指针始终保持128位对齐,例如vld1.32 {q0},[r1:128] 您还可以使用postincrement版本(我也不知道内部函数会生成该版本),例如vld1.32 {q0},[r1:128]! 在1GHz处理器上,每128位块约有95个周期,因此4000个元素的95us听起来很慢。假设您正在使用缓存,那么您应该能够做得更好。这个数字是关于如果您受外部存储器速度的约束所期望的。
席陋临拈
对于Neon优化而言要记住的重要内容...它有两个管道,一个用于装载/存储(带有2条指令队列-一个正在等待执行,一个正在运行-通常每个执行3-9个周期),以及一个用于算术运算(具有2条指令流水线,一条执行并保存结果。)只要您保持这两个管道繁忙并交错您的指令,它就会非常快地工作。更好的是,如果您有ARM指令,那么只要您留在寄存器中,就不必等待NEON完成,它们将同时执行(高速缓存中最多8条指令)!因此,您可以在ARM指令中建立一些基本的循环逻辑,并且它们将同时执行。 您的原始代码也只使用了4个寄存器中的一个(q寄存器具有4个32位值)。其中3个没有明显原因进行了加倍操作,因此您的速度是原来的4倍。 在此代码中最好的办法是对该循环进行处理,方法是在
之后添加
,从而对它们进行嵌入处理,依此类推。您还会看到我在发送结果之前还要再等待1条指令,因此管道永远不会等待其他指令。最后,注意“ 9”,表示后递增。因此它读取/写入该值,然后自动从寄存器中递增指针。我建议您不要在ARM代码中使用该寄存器,因此它不会挂起自己的管道...使您的寄存器分开,在ARM端有一个冗余的
变量。 最后一部分...我说的可能是正确的,但并非总是如此。这取决于您当前的Neon版本。时间可能会在将来发生变化,或者可能并非一直如此。 ymmv,它对我有用。