我正在尝试使用NEON优化OpenCV代码的某些部分。这是我工作的原始代码块。(注意:如果它是重要的,您可以在"opencvfolder/modules/video/src/lkpyramid.cpp".找到完整的源代码。它是一个目标跟踪算法的实现。)
for( ; x < colsn; x++ )
{
deriv_type t0 = (deriv_type)(trow0[x+cn] - trow0[x-cn]);
deriv_type t1 = (deriv_type)((trow1[x+cn] + trow1[x-cn])*3 + trow1[x]*10);
drow[x*2] = t0; drow[x*2+1] = t1;
}在此代码中,deriv_type的大小为2字节。这是我写的霓虹灯大会。使用原始代码,我测量10-11 fps。用霓虹灯更糟,我只能得到5-6 fps。我对霓虹灯不太了解,这段代码可能有很多错误。我哪里做错了?谢谢
for( ; x < colsn; x+=4 )
{
__asm__ __volatile__(
"vld1.16 d2, [%2] \n\t" // d2 = trow0[x+cn]
"vld1.16 d3, [%3] \n\t" // d3 = trow0[x-cn]
"vsub.i16 d9, d2, d3 \n\t" // d9 = d2 - d3
"vld1.16 d4, [%4] \n\t" // d4 = trow1[x+cn]
"vld1.16 d5, [%5] \n\t" // d5 = trow1[x-cn]
"vld1.16 d6, [%6] \n\t" // d6 = trow1[x]
"vmov.i16 d7, #3 \n\t" // d7 = 3
"vmov.i16 d8, #10 \n\t" // d8 = 10
"vadd.i16 d4, d4, d5 \n\t" // d4 = d4 + d5
"vmul.i16 d10, d4, d7 \n\t" // d10 = d4 * d7
"vmla.i16 d10, d6, d8 \n\t" // d10 = d10 + d6 * d8
"vst2.16 {d9,d10}, [%0] \n\t" // drow[x*2] = d9; drow[x*2+1] = d10;
//"vst1.16 d4, [%1] \n\t"
: //output
:"r"(drow+x*2), "r"(drow+x*2+1), "r"(trow0+x+cn), "r"(trow0+x-cn), "r"(trow1+x+cn), "r"(trow1+x-cn), "r"(trow1) //input
:"d2", "d3", "d4", "d5", "d6", "d7", "d8", "d9", "d10" //registers
);
}编辑
这是与本质有关的真理。这几乎和以前一样。效果还是很慢。
const int16x8_t vk3 = { 3, 3, 3, 3, 3, 3, 3, 3 };
const int16x8_t vk10 = { 10, 10, 10, 10, 10, 10, 10, 10 };
for( ; x < colsn; x+=8 )
{
int16x8x2_t loaded;
int16x8_t t0a = vld1q_s16(&trow0[x + cn]);
int16x8_t t0b = vld1q_s16(&trow0[x - cn]);
loaded.val[0] = vsubq_s16(t0a, t0b); // t0 = (trow0[x + cn] - trow0[x - cn])
loaded.val[1] = vld1q_s16(&trow1[x + cn]);
int16x8_t t1b = vld1q_s16(&trow1[x - cn]);
int16x8_t t1c = vld1q_s16(&trow1[x]);
loaded.val[1] = vaddq_s16(loaded.val[1], t1b);
loaded.val[1] = vmulq_s16(loaded.val[1], vk3);
loaded.val[1] = vmlaq_s16(loaded.val[1], t1c, vk10);
}发布于 2012-07-23 12:26:36
由于数据风险,您正在创建许多管道阻塞。例如,这三个指令:
"vadd.i16 d4, d4, d5 \n\t" // d4 = d4 + d5
"vmul.i16 d10, d4, d7 \n\t" // d10 = d4 * d7
"vmla.i16 d10, d6, d8 \n\t" // d10 = d10 + d6 * d8他们每人只需要发出一条指令,但是由于结果还没有准备好,他们之间有几个循环暂停(霓虹灯指令调度)。
尝试展开循环几次,并交错他们的指令。如果使用本质,编译器可能会为您执行此操作。在指令、调度等方面击败编译器并不是不可能的,但这是相当困难的,而且通常也不值得(这可能是因为没有过早地进行优化)。
编辑
您的内部代码是合理的,我怀疑编译器只是做得不太好。看看它正在生成的组装代码(objdump -d),您可能会发现它也造成了许多管道危险。编译器的后期版本可能会有所帮助,但如果没有,您可能需要自己修改循环以隐藏结果的延迟(您将需要指令时间)。保持当前的代码,因为它是正确的,并应优化由一个聪明的编译器。
你可能会得到这样的结果:
// do step 1 of first iteration
// ...
for (int i = 0; i < n - 1; i++) {
// do step 1 of (i+1)th
// do step 2 of (i)th
// with their instructions interleaved
// ...
}
// do step 2 of (n-1)th
// ...您还可以将循环拆分成多个步骤,或者将循环展开几次(例如,将i++更改为i+=2,将循环体加倍,在下半部分将i更改为i+1 )。我希望这个答案有帮助,如果有什么不清楚的话,让我知道!
发布于 2012-07-23 11:05:32
有一些循环不变的东西需要移到for循环之外--这可能有点帮助。
您还可以考虑使用全宽度SIMD操作,这样您可以处理每个循环迭代8 ppint,而不是4。
但是,最重要的是,您可能应该使用本质,而不是原始的asm,这样编译器就可以处理peephole优化、寄存器分配、指令调度、循环展开等等。
例如。
// constants - init outside loop
const int16x8_t vk3 = { 3, 3, 3, 3, 3, 3, 3, 3 };
const int16x8_t vk10 = { 10, 10, 10, 10, 10, 10, 10, 10 };
for( ; x < colsn; x += 8)
{
int16x8_t t0a = vld1q_s16(&trow0[x + cn]);
int16x8_t t0b = vld1q_s16(&trow0[x - cn]);
int16x8_t t0 = vsubq_s16(t0a, t0b); // t0 = (trow0[x + cn] - trow0[x - cn])
// ...
}https://stackoverflow.com/questions/11611039
复制相似问题