代码之家  ›  专栏  ›  技术社区  ›  CuriousGeorge

手臂霓虹灯换位4x4 uint32

  •  2
  • CuriousGeorge  · 技术社区  · 9 年前

    我试着将图像逆时针旋转90度,然后水平翻转。

    我的第一个方法是使用OpenCV:

    cv::transpose(in, tmp); // transpose around top left
    cv::flip(tmp, out, -1); // flip on both axes
    

    为了提高性能,我尝试将这两个函数合并为一个。

    我的代码:

    void ccw90_hflip_640x480(const cv::Mat& img, cv::Mat& out)
    {
        assert(img.cols == 640 && img.rows == 480);
        assert(out.cols == 480 && out.cols == 640);
    
        uint32_t* imgData = (uint32_t*)img.data;
        uint32_t* outData = (uint32_t*)out.data;
    
        uint32_t *pRow = imgData;
        uint32_t *pEnd = imgData + (640 * 480);
        uint32_t *dstCol = outData + (480 * 640) - 1;
    
        for( ; pRow != pEnd; pRow += 640, dstCol -= 1)
        {
            for(uint32_t *ptr = pRow, *end = pRow + 640, *dst = dstCol;
                ptr != end;
                ++ptr, dst -= 480)
            {
                *dst = *ptr;
            }
        }
    }
    

    我以为上面的方法会更快,但事实并非如此。除了使用NEON的OpenCV之外,我想不出有什么原因它不会更快。

    我发现这篇文章/演示文稿: http://shervinemami.info/NEON_RotateBGRX.swf

    换位和翻转以一种方式模糊在一起,这使得很难修改到它将以另一种方式旋转的位置,并且像我需要的那样围绕水平轴翻转。这篇文章很老,所以我希望有一个更直接的方法来做我需要的事情。

    那幺,使用手臂NEON换位uint32的4x4矩阵最简单的方法是什么?

    2 回复  |  直到 9 年前
        1
  •  2
  •   CuriousGeorge    9 年前

    下面的代码相当于原始帖子中的OpenCV调用,但执行速度要快几倍(至少在我的设备上)。

    使用Neon确实显著提高了性能。由于换位发生在CPU内部,内存访问可以简化为以四个像素为一组来读取和写入像素,而不是像评论中所讨论的那样一次一个像素。

    void ccw90_hflip_640x480_neon(const cv::Mat& img, cv::Mat& out)
    {
        assert(img.cols == 640 && img.rows == 480);
        assert(out.cols == 480 && out.cols == 640);
    
        uint32_t *pRow = (uint32_t*)img.data;
        uint32_t *pEnd = (uint32_t*)img.data + (640 * 480);
        uint32_t *dstCol = (uint32_t*)out.data + (480 * 640) - (480 * 3) - 4;
    
        for( ; pRow != pEnd; pRow += 640 * 4, dstCol -= 4)
        {
            for(uint32_t *ptr = pRow, *end = pRow + 640, *dst = dstCol;
                ptr != end;
                ptr += 4, dst -= 480 * 4)
            {
                uint32_t* in0 = ptr;
                uint32_t* in1 = in0 + 640;
                uint32_t* in2 = in1 + 640;
                uint32_t* in3 = in2 + 640;
    
                uint32_t* out0 = dst;
                uint32_t* out1 = out0 + 480;
                uint32_t* out2 = out1 + 480;
                uint32_t* out3 = out2 + 480;
    
                asm("vld1.32 {d0, d1}, [%[in0]]    \n"
                    "vld1.32 {d2, d3}, [%[in1]]    \n"
                    "vld1.32 {d4, d5}, [%[in2]]    \n"
                    "vld1.32 {d6, d7}, [%[in3]]    \n"
                    "vtrn.32 q0, q1                \n"
                    "vtrn.32 q2, q3                \n"
                    "vswp d1, d4                   \n"
                    "vswp d3, d6                   \n"
                    "vrev64.32 q0, q0              \n"
                    "vrev64.32 q1, q1              \n"
                    "vrev64.32 q2, q2              \n"
                    "vrev64.32 q3, q3              \n"
                    "vswp d0, d1                   \n"
                    "vswp d2, d3                   \n"
                    "vswp d4, d5                   \n"
                    "vswp d6, d7                   \n"
                    "vst1.32 {d6, d7}, [%[out0]]   \n"
                    "vst1.32 {d4, d5}, [%[out1]]   \n"
                    "vst1.32 {d2, d3}, [%[out2]]   \n"
                    "vst1.32 {d0, d1}, [%[out3]]   \n"
                    :
                    : [out0] "r" (out0), [out1] "r" (out1), [out2] "r" (out2), [out3] "r" (out3),
                        [in0] "r" (in0), [in1] "r" (in1), [in2] "r" (in2), [in3] "r" (in3)
                    : "d0", "d1", "d2", "d3", "d4", "d5", "d6", "d7"
                    );
            }
        }
    }
    
        2
  •  0
  •   artless-noise-bye-due2AI    9 年前

    霓虹灯不会有很大帮助 笔记 。您的代码只是移动数据;霓虹灯不能使底层内存的速度显著提高。看见 this article ; 使用PLD也会有帮助。我建议你处理 dst 按顺序跳来跳去 ptr 。缓存将预填充 聚四氟乙烯(ptr) 分布式存储系统 将行填充。

    这里是遍历内存的另一种形式(变量名称可能没有意义),

    uint32_t *pEnd = imgData + 640;
    uint32_t *dstCol = outData;
    
    for( ; pRow != pEnd; pRow ++)
    {
        for(uint32_t *ptr = pRow, *dst = dstCol, *end = dst + 480;
            dst != end;
            ptr += 640, dst++)
        {
            *dst = *ptr;
        }
        // could flush `dstCol` here as it is complete or hope the system clues in.
        dstCol += 480;
    }
    

    这个想法是填补 分布式存储系统 按顺序跳转访问 imgData 如果你按顺序写,所有现代的记忆都会更有效率。高速缓存和同步DRAM通常一次填充几个32位字。我们可以利用L1缓存的知识展开内部循环。它是32或64字节,表示8或16个32位像素。填充量将类似,因此可以将转置减少到 可缓存的 块并一次处理每个块。将640x480图像看作是由8*8像素块(最小L1缓存大小)组成的,并依次处理每个块。

    完成此操作后,NEON指令可能会获得一定百分比。但是,优化加载/存储单元(所有CPU单元共用)应该是第一步。

    注释 :Neon是SIMD(单指令,多数据),它擅长对像素进行数字运算,通过一次处理几个像素来提高计算能力。它确实有一些优化内存遍历的指令,但底层内存对于CORE CPU单元和SIMD/NEON单元是相同的。NEON可能会起到提振作用,但我认为在优化内存系统的访问顺序之前,这是徒劳的。