手臂霓虹灯转置 4x4 uint32

arm neon transpose 4x4 uint32

我正在尝试逆时针旋转成像 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 之外,我想不出它不会更快的任何原因。

我找到了这个 article/presentation: http://shervinemami.info/NEON_RotateBGRX.swf

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

那么使用 arm NEON 转置 uint32 的 4x4 矩阵的最简单方法是什么?

霓虹灯不会有太大帮助注意。您的代码只是在移动数据; neon 不能使底层内存显着加快。参见this article;使用 PLD 也会有所帮助。我建议您按顺序处理 dst 并使用 ptr 跳转。缓存将预填充 ptr 并且 dst 将行填充。

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

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;
}

想法是按顺序填充 dst 并跳转访问 imgData。如果你按顺序写出来,所有现代记忆都会更有效地填满。高速缓存和同步 DRAM 通常一次填充几个 32 位字。我们可以在了解 L1 缓存的情况下展开内部循环。它是 32 或 64 字节,代表 8 或 16 个 32 位像素。填充量将是相似的,因此您可以将转置减少到 cacheable 块并一次处理每个块。将 640x480 图像视为由 8*8 像素图块(最小 L1 缓存大小)组成并依次处理每个图块。

执行此操作后,NEON 指令可能会增加一些百分比。但是,优化 load/store 单元(所有 CPU 单元通用)应该是第一步。

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

以下代码等同于原始 post 中的 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"
                );
        }
    }
}