我在霓虹灯和手臂组装中实现了计算机视觉卷积算法,其中每个像素被九个自身和相邻像素的和积所代替。主循环看起来像这样:
.loop:
vld1.u8 {d0}, [line_prev]
add line_prev, line_prev, #1
vld1.u8 {d1}, [line_prev]
add line_prev, line_prev, #1
vld1.u8 {d2}, [line_prev]
add line_prev, line_prev, #6
vld1.u8 {d3}, [line]
add line, line, #1
vld1.u8 {d4}, [line]
add line, line, #1
vld1.u8 {d5}, [line]
add line, line, #6
vld1.u8 {d6}, [line_next]
add line_next, line_next, #1
vld1.u8 {d7}, [line_next]
add line_next, line_next, #1
vld1.u8 {d8}, [line_next]
add line_next, line_next, #6
//Everything is loaded now. Let's multiply and sum
vmull.u8 q10, d0, d10 //d10 to d18 holds the kernel matrix values
vmlal.u8 q10, d1, d11
vmlal.u8 q10, d2, d12
vmlal.u8 q10, d3, d13
vmlal.u8 q10, d4, d14
vmlal.u8 q10, d5, d15
vmlal.u8 q10, d6, d16
vmlal.u8 q10, d7, d17
vmlal.u8 q10, d8, d18
vshrn.u16 d4, q10, d19 //Shift the sum by the value in d19
vst1.u8 {d4}, [out]! //Store result
subs temp, temp, #8 //We have processed 8 pixels
bgt .loop
如何优化(在速度方面)此循环?是否有更聪明的事情来加载像素。此外,q11 ...可用:我应该使用它们来并行获得vmull和mlal指令吗?
答案 0 :(得分:5)
尝试交错加载代码和数学代码。你想要一些加载/使用延迟(瞄准几个周期),但交错的负载和数学通常更好。
一次加载Q寄存器以获取16个值可能有助于而不是D寄存器,如果你可以使用寄存器空间来适应它。根据上面的流加载/使用有帮助(你可以经常使用一个物理在一次迭代中注册两个值。)
使用预加载。
答案 1 :(得分:2)
如果可能的话,每行加载3次相同的数据几乎肯定是值得的。如果数据的大小不合适,可能更难以避免未对齐的负载,但will cost an extra cycle是值得注意的。
我尝试的方法是保留两个寄存器'移动数据的价值,并使用vext
从对中提取偏移数据,每次迭代节省两个负载和几个周期,代价是一个额外的寄存器,如:
vld1.u8 {d0}, [line]! ; 'initial' chunk
...
.loop:
vld1.u8 {d3}, [line]! ; 'next' chunk
...
vext.8 d1, d0, d3, #1
vext.8 d2, d0, d3, #2
; do stuff with d0=line, d1=line+1, d2=line+2
...
vmov d0, d3 ; 'next' chunk becomes 'current' chunk for the next iteration
...
bgt .loop
正如其他人所提到的,如果您乐意针对特定的微体系结构进行定位,那么仔细调整的手动预加载对于较旧的内核非常有用。在我的脑海中,我认为Cortex-A9的最佳位置往往是2个缓存线。请注意,如果图像与L1缓存相比足够小,前一行保持热线,则可能只需要在line_next
之前预取。