Search code examples
c++linuxarmmemcpyneon

Eventual ARM Linux Memory Fragmentation with NEON Copy but not memcpy


I am running Linux 4.4 on a BeagleBone X-15 ( ARM Cortex-A15 ) board. My application mmaps the output of the SGX GPU and needs to copy the DRM backing store.

Both memcpy and my custom NEON copy code work... but the NEON code is much faster ( ~11ms vs. ~35ms ).

I notice that, pretty consistently, after 12500 seconds, when I am using the NEON version of the copy, Linux kills the application for out of memory ( OOM ). When I run the application and change the one line from NEON copy to standard memcpy, it runs indefinitely ( 12 hours so far... ). But it is slower to copy.

I've pasted the mmap, copy, and NEON copy code below. Is there something really wrong with my NEON copy?

NEON Copy:

/**
* CompOpenGL neonCopyRGBAtoRGBA()
* Purpose: neonCopyRGBAtoRGBA - Software NEON copy
*
* @param src - Source buffer
* @param dst - Destination buffer
* @param numpix - Number of pixels to convert
*/
__attribute__((noinline)) void CompOpenGL::neonCopyRGBAtoRGBA(unsigned char* src, unsigned char* dst, int numPix)
{

    (void)src;
    (void)dst;
    (void)numPix;

    // This case takes RGBA -> BGRA
    __asm__ volatile(
                "mov r3, r3, lsr #3\n"           /* Divide number of pixels by 8 because we process them 8 at a time */
                "loopRGBACopy:\n"
                "vld4.8 {d0-d3}, [r1]!\n"        /* Load 8 pixels into d0 through d2. d0 = R[0-7], d1 = G[0-7], d2 = B[0-7], d3 = A[0-7] */
                "subs r3, r3, #1\n"              /* Decrement the loop counter */
                "vst4.8 {d0-d3}, [r2]!\n"        /* Store the RGBA into destination 8 pixels at a time */
                "bgt loopRGBACopy\n"
                "bx lr\n"
                );

}

Mmap and copy code here:

union gbm_bo_handle handleUnion = gbm_bo_get_handle(m_Fb->bo);
struct drm_omap_gem_info gemInfo;
char *gpuMmapFrame = NULL;
gemInfo.handle = handleUnion.s32;
int ret = drmCommandWriteRead(m_DRMController->m_Fd, DRM_OMAP_GEM_INFO,&gemInfo, sizeof(gemInfo));
if (ret) {
    qDebug() << "Cannot set write/read";
}
else {
    // Mmap the frame
    gpuMmapFrame = (char *)mmap(0, gemInfo.size, PROT_READ | PROT_WRITE, MAP_SHARED,m_DRMController->m_Fd, gemInfo.offset);

    if ( gpuMmapFrame != MAP_FAILED ) {

        QElapsedTimer timer;
        timer.restart();
        
        //m_OGLController->neonCopyRGBAtoRGBA((uchar*)gpuMmapFrame,  (uchar*)m_cpyFrame,dmaBuf.width * dmaBuf.height);
        memcpy(m_cpyFrame,gpuMmapFrame,dmaBuf.height * dmaBuf.width * 4);
        
        qDebug() << "Copy Performance: " << timer.elapsed();

Solution

  • The good news is that your function will run considerably faster, if you replace vld4/vst4 with vld1/vst1.

    The bad news is that you have to report which registers you use and modify, including the CPSR and memory, and you shouldn't return from inline assembly. (bx lr).

    __asm__ volatile(
                    "mov r3, r3, lsr #3\n"           /* Divide number of pixels by 8 because we process them 8 at a time */
                    "loopRGBACopy:\n"
                    "vld1.8 {d0-d3}, [r1]!\n"        /* Load 8 pixels into d0 through d2. d0 = R[0-7], d1 = G[0-7], d2 = B[0-7], d3 = A[0-7] */
                    "subs r3, r3, #1\n"              /* Decrement the loop counter */
                    "vst1.8 {d0-d3}, [r2]!\n"        /* Store the RGBA into destination 8 pixels at a time */
                    "bgt loopRGBACopy\n"
                    ::: "r1", "r2", "r3", "d0", "d1", "d2", "d3", "cc", "memory"
                    );
    

    http://www.ethernut.de/en/documents/arm-inline-asm.html