我有2个功能,用于将YUYV帧分割为Y / U / V独立平面。我这样做是为了通过将包含Y / U / V数据的3个纹理上传到GPU,在OpenGL ES 2.0着色器中执行从YUYV视频帧到RGBA的格式转换。其中一个函数是用C ++编写的,一个是用ARM NEON编写的。我的目标是Cortex-A15 AM57xx Sitara。
我希望NEON代码的性能优于C ++代码,但它们的表现相同。一种可能性是我内存I / O绑定。另一种可能性是我不擅长编写NEON代码..
为什么这两个功能的表现相同?对这两种功能都有明显的优化吗?
霓虹灯功能:
minSdkVersion
C ++函数:
/// This structure is passed to ARM Assembly code
/// to split the YUV frame into seperate planes for
/// OpenGL Consumption
typedef struct {
char *input_data;
int input_size;
char *y_plane;
char *u_plane;
char *v_plane;
} yuvSplitStruct;
void TopOpenGL::splitYuvPlanes(yuvSplitStruct *yuvStruct)
{
__asm__ volatile(
"PUSH {r4}\n" /* Save callee-save registers R4 and R5 on the stack */
"PUSH {r5}\n" /* r1 is the pointer to the input structure ( r0 is 'this' because c++ ) */
"ldr r0 , [r1]\n" /* reuse r0 scratch register for the address of our frame input */
"ldr r2 , [r1, #4]\n" /* use r2 scratch register to store the size in bytes of the YUYV frame */
"ldr r3 , [r1, #8]\n" /* use r3 scratch register to store the destination Y plane address */
"ldr r4 , [r1, #12]\n" /* use r4 register to store the destination U plane address */
"ldr r5 , [r1, #16]\n" /* use r5 register to store the destination V plane address */
"/* pld [r0, #192] PLD Does not seem to help */"
"mov r2, r2, lsr #5\n" /* Divide number of bytes by 32 because we process 16 pixels at a time */
"loopYUYV:\n"
"vld4.8 {d0-d3}, [r0]!\n" /* Load 8 YUYV elements from our frame into d0-d3, increment frame pointer */
"vst2.8 {d0,d2}, [r3]!\n" /* Store both Y elements into destination y plane, increment plane pointer */
"vmov.F64 d0, d1\n" /* Duplicate U value */
"vst2.8 {d0,d1}, [r4]!\n" /* Store both U elements into destination u plane, increment plane pointer */
"vmov.F64 d1, d3\n" /* Duplicate V value */
"vst2.8 {d1,d3}, [r5]!\n" /* Store both V elements into destination v plane, increment plane pointer */
"subs r2, r2, #1\n" /* Decrement the loop counter */
"bgt loopYUYV\n" /* Loop until entire frame is processed */
"POP {r5}\n" /* Restore callee-save registers */
"POP {r4}\n"
);
}
答案 0 :(得分:4)
这个问题可能涉及两个不同的因素: 1.霓虹灯指令比“常规”ARM指令更快吗? 2.我可以编写比编译器更好的汇编吗?
霓虹灯指令比“常规”ARM指令更快吗?
您的算法仅涉及加载数据并将其存储在其他位置。在A15上,架构中的加载/存储管道为NEON寄存器和ARM寄存器共享。这可能不完整,但过去A8和A9可能存在的任何好处,它们具有不同的加载/存储流水线以及不同的指令发布逻辑,不同的指令重新排序和分支预测功能。因此,在A15上,当考虑NEON指令与常规ARM指令时,这些考虑不再是一个重要因素。即使在那时,memcpy在ARM指令中也比在NEON中更快。
现在很老的A8的一个很好的介绍是http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.faqs/ka13544.html。
A15上超标量体系结构的视图:http://www.extremetech.com/wp-content/uploads/2012/11/Cortex-A15Block.jpg
您可以与A8进行比较:
http://courses.cecs.anu.edu.au/courses/ENGN8537/notes/images/processor/arm-a8-pipeline.png
请注意,在A8上,NEON是一个非常独立的区块,但在A15上,很多东西都是共享的。
我可以编写比编译器更好的汇编吗?
也许,但是对于现代架构,这现在涉及对微架构的越来越深刻的理解,特别是对于仅仅是数据置换/交织的操作。如果您正在编写实际涉及乘法的更复杂的数据处理,那么通常您可以比编译器做得更好,特别是将您的循环展开调整为乘法的回写延迟。展开循环需要花费精力来说服编译器执行操作,因为这通常会限制数据的长度(例如,它是4的倍数)。使用加载/存储时,由于数学运算没有写回延迟,因此不需要进行有趣的优化。
流水线处理器架构上有很多东西,但是
https://en.wikipedia.org/wiki/Classic_RISC_pipeline#Writeback