Raspberry Pi 5上的Neon加速RGB2GRay,128位(Q寄存器)比64位(D寄存器)慢,为什么?

问题描述 投票:0回答:1

如标题所示,我有一个 Raspberry Pi 5(Armv8 的 Cortex-A76;四核),我使用 OpenCV 在上面做一些事情。

我使用cv::cvtColor获取RGB2Gray,在Raspberry Pi 5上速度很慢,所以我使用neon_arm.h来优化速度,确实更快了。

在版本 1 中,我使用 vld3_u8(),它每次从图像加载 uint8x8x3_t 数据(64 位寄存器)。

在 Version-2 I 中,使用 vld3q_u8() ,每次从图像中加载 uint8x16x3_t 数据(128 位)。

我使用 std::chrono 对代码进行计时;奇怪的是,版本 1 比版本 2 更快(在我看来应该更慢)。

版本1:10.5ms(100次平均)。

版本2:12.7ms(100次平均)。

照片尺寸为3009x4248。

我不知道是否有什么问题(可能是设备或代码?)。

谁能教我为什么?

我的代码如下所示。


void neon_cv_8(cv::Mat &src,cv::Mat &grey,std::size_t size)
{
    uint8_t *p_img = src.data;
    uint8_t *p_o = grey.data;
    uint8x8_t r_corr = vdup_n_u8(76);
    uint8x8_t g_corr = vdup_n_u8(150);
    uint8x8_t b_corr = vdup_n_u8(30);


    for(std::size_t i = 0; i<size ; i+=8)
    {
        uint8x8x3_t vdata = vld3_u8(p_img);
        uint16x8_t tmp = vmull_u8(b_corr, vdata.val[0]);
        tmp = vmlal_u8(tmp, g_corr, vdata.val[1]);
 
        tmp = vmlal_u8(tmp, r_corr, vdata.val[2]);
        uint8x8_t vgray = vshrn_n_u16(tmp, 8);
        
        vst1_u8(p_o, vgray);
        p_o += 8;
        p_img += 8 * 3;
    }
}//version-1

void neon_cv_16(cv::Mat &src,cv::Mat &grey,std::size_t size)
{
    uint8_t *p_img = src.data;
    uint8_t *p_o = grey.data;
    uint16x4_t r_corr = vdup_n_u16(76);
    uint16x4_t g_corr = vdup_n_u16(150);
    uint16x4_t b_corr = vdup_n_u16(30);


    for(std::size_t i = 0; i<size ; i+=2*8)
    {
        
        uint8x16x3_t vdata = vld3q_u8(p_img);
        uint16x8_t v_b = vmovl_u8(vget_low_u8(vdata.val[0])),
                    v_g = vmovl_u8(vget_low_u8(vdata.val[1])),
                    v_r = vmovl_u8(vget_low_u8(vdata.val[2]));

        uint32x4_t tmp_low = vmull_u16(b_corr, vget_low_u16(v_b));
        uint32x4_t tmp_high = vmull_u16(b_corr,vget_high_u16(v_b));

        tmp_low = vmlal_u16(tmp_low,g_corr, vget_low_u16(v_g));
        tmp_high = vmlal_u16(tmp_high,g_corr,vget_high_u16(v_g));

        tmp_low = vmlal_u16(tmp_low,r_corr, vget_low_u16(v_r));
        tmp_high = vmlal_u16(tmp_high,r_corr,vget_high_u16(v_r));
        uint8x8_t vgray0 = vqmovn_u16(vcombine_u16(vrshrn_n_u32(tmp_low,8)
                        ,vrshrn_n_u32(tmp_high,8)));
        
        v_r = vmovl_u8(vget_high_u8(vdata.val[0])),
        v_g = vmovl_u8(vget_high_u8(vdata.val[1])),
        v_b = vmovl_u8(vget_high_u8(vdata.val[2]));
        
        tmp_low = vmull_u16(b_corr, vget_low_u16(v_b));
        tmp_high = vmull_u16(b_corr,vget_high_u16(v_b));

        tmp_low = vmlal_u16(tmp_low,g_corr, vget_low_u16(v_g));
        tmp_high = vmlal_u16(tmp_high,g_corr,vget_high_u16(v_g));

        tmp_low = vmlal_u16(tmp_low,r_corr, vget_low_u16(v_r));
        tmp_high = vmlal_u16(tmp_high,r_corr,vget_high_u16(v_r));
        uint8x8_t vgray1 = vqmovn_u16(vcombine_u16(vrshrn_n_u32(tmp_low,8)
                        ,vrshrn_n_u32(tmp_high,8)));
        
        vst1q_u8(p_o, vcombine_u8(vgray0,vgray1));
        p_o += 16;
        p_img += 48;
    }
}//version-2
c++ gcc arm simd neon
1个回答
0
投票

与旧的 32 位 ARMv7 不同,ARM64 SIMD 为 16 字节宽。像 vget_high_something 这样的内在函数不再是免费的;它们编译成随机播放指令。不过,vget_low_something 仍然是免费的。

您应该使用

vmull_high_u16
vmlal_high_u16
对向量的上半部分进行数学运算,而无需进行洗牌。同样,
vqmovn_high_u16
将结果减少为字节。

© www.soinside.com 2019 - 2024. All rights reserved.