ARM NEON优化不比C ++指针实现快

时间:2016-05-20 18:56:08

标签: assembly optimization arm neon yuv

我有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"
    );

}

1 个答案:

答案 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