了解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;
}
想知道数组和向量之间的加载/存储操作是否消耗了更多的时间来抵消并行添加的好处。
更新:更多信息以回应伊戈尔的回复
1.代码张贴在这里:
plain.c
plain.s
neon.c
neon.s
从两个装配清单中的部分(标签)L7,我看到霓虹灯版本有更多的装配说明。(因此需要更多时间?)
2.我在arm-gcc上使用-mfpu = neon编译,没有其他标志或优化。对于普通版本,根本没有编译器标志。
那是一个错字,SIZE本来就是大小;两者都是一样的。
4,5。由4000个元素组成。我在函数调用之前和之后使用gettimeofday()定时.NEON = 230us,普通= 155us
是的,我在每种情况下都打印了元素
除此之外,没有任何进步。
答案 0 :(得分:4)
这样的事情可能会更快一些。
void double_elements(unsigned int *ptr, unsigned int size)
{
unsigned int i;
uint32x4_t Q0,Q1,Q2,Q3;
for( i=0;i<(SIZE/16);i++)
{
Q0=vld1q_u32(ptr);
Q1=vld1q_u32(ptr+4);
Q0=vaddq_u32(Q0,Q0);
Q2=vld1q_u32(ptr+8);
Q1=vaddq_u32(Q1,Q1);
Q3=vld1q_u32(ptr+12);
Q2=vaddq_u32(Q2,Q2);
vst1q_u32(ptr,Q0);
Q3=vaddq_u32(Q3,Q3);
vst1q_u32(ptr+4,Q1);
vst1q_u32(ptr+8,Q2);
vst1q_u32(ptr+12,Q3);
ptr+=16;
}
return;
}
原始代码存在一些问题(优化程序可能会修复其中一些问题但其他问题可能没有,您需要在生成的代码中进行验证):
如果您使用内联汇编编写此内容,您还可以:
答案 1 :(得分:3)
这个问题相当模糊,你没有提供太多信息,但我会尽力给你一些指示。
size
,第二个函数使用SIZE
,这是故意的吗?它们是一样的吗?vshl_n_u32
)而不是添加。编辑:谢谢你的回答。我看了一下,找到了this discussion,它说(强调我的):
将数据从NEON移动到ARM寄存器 是Cortex-A8很贵,所以NEON在 Cortex-A8最适用于大 小ARM的工作块 管道互动。
在你的情况下,没有NEON到ARM的转换,只有加载和存储。但是,非NEON部件似乎消耗了并行操作的节省。我希望在NEON中执行很多操作的代码可以获得更好的结果,例如颜色转换。
答案 2 :(得分:3)
按指令处理更大的数量,并交错加载/存储和交错使用。此功能目前翻倍(向左移动)56 uint。
void shiftleft56(const unsigned int* input, unsigned int* output)
{
__asm__ (
"vldm %0!, {q2-q8}\n\t"
"vldm %0!, {q9-q15}\n\t"
"vshl.u32 q0, q2, #1\n\t"
"vshl.u32 q1, q3, #1\n\t"
"vshl.u32 q2, q4, #1\n\t"
"vshl.u32 q3, q5, #1\n\t"
"vshl.u32 q4, q6, #1\n\t"
"vshl.u32 q5, q7, #1\n\t"
"vshl.u32 q6, q8, #1\n\t"
"vshl.u32 q7, q9, #1\n\t"
"vstm %1!, {q0-q6}\n\t"
// "vldm %0!, {q0-q6}\n\t" if you want to overlap...
"vshl.u32 q8, q10, #1\n\t"
"vshl.u32 q9, q11, #1\n\t"
"vshl.u32 q10, q12, #1\n\t"
"vshl.u32 q11, q13, #1\n\t"
"vshl.u32 q12, q14, #1\n\t"
"vshl.u32 q13, q15, #1\n\t"
// lost cycle here unless you overlap
"vstm %1!, {q7-q13}\n\t"
: "=r"(input), "=r"(output) : "0"(input), "1"(output)
: "q0", "q1", "q2", "q3", "q4", "q5", "q6", "q7",
"q8", "q9", "q10", "q11", "q12", "q13", "q14", "q15", "memory" );
}
重要的是要记住Neon优化......它有两个管道,一个用于加载/存储(带有2个指令队列 - 一个待处理,一个正在运行 - 通常每个需要3-9个周期),另一个用于算术运算(使用2个指令管道,一个执行,一个保存其结果)。只要您保持这两个管道繁忙并交错指示,它就会非常快速地工作。更好的是,如果你有ARM指令,只要你留在寄存器中,它就永远不必等待NEON完成,它们将同时执行(高速缓存中最多8条指令)!因此,您可以在ARM指令中设置一些基本的循环逻辑,它们将同时执行。
您的原始代码也仅使用4个寄存器值(q寄存器有4个32位值)。他们中的3人在没有明显原因的情况下进行了双倍的操作,所以你的速度是你原来的4倍。
此代码中更好的是针对此循环,通过在vldm %0!, {q2-q8}
之后添加vstm %1!
来处理它们,等等。你还看到我在发送结果之前再等一个指令,所以管道永远不会等待别的东西。最后,请注意!
,这意味着后增量。因此它读取/写入值,然后自动从寄存器递增指针。我建议你不要在ARM代码中使用该寄存器,因此它不会挂起自己的管道......保持寄存器分离,在ARM端有一个冗余的count
变量。
最后一部分......我所说的可能是真的,但并非总是如此。这取决于您目前的霓虹灯修订版。时间可能在未来发生变化,或者可能并非总是如此。它适用于我,ymmv。