【问题标题】:Optimizing Code Used to Convolve Image优化用于卷积图像的代码
【发布时间】:2017-11-12 04:04:10
【问题描述】:

以下代码用于对图像进行卷积。所述图像的每个像素由以下表示:

typedef struct {
    unsigned short red; /* R value */
    unsigned short green; /* G value */
    unsigned short blue; /* B value */
} pixel;

可以看出,RGB 值具有 16 位表示(“16 位颜色”)。图像 I 存储为一维像素数组,其中第 (i, j) 个像素为 I[RIDX(i,j,n)]。这里 n 是图像矩阵的维数,并且 RIDX是一个宏定义如下

#define RIDX(i,j,n) ((i)*(n)+(j))

在大多数情况下,您可以将 I[RIDX(i,j,n)] 视为等效于 I[i][j]。最后,我需要使用代码移动、循环展开和阻塞等技术优化以下代码。

char naive_convolve_descr[] = "naive_convolve: Naive baseline implementation";
void naive_convolve(int dim, pixel *src, pixel *dst)
{
    int i, j, ii, jj, curI, curJ;
    pixel_sum ps;

    for (j = 0; j < dim; j++){
        for (i = 0; i < dim; i++){
            ps.red    = 0.0;
            ps.green  = 0.0;
            ps.blue   = 0.0;
            ps.weight = 0.0;
            for (jj = -2; jj <= 2; jj++){
                for (ii = -2; ii <= 2; ii++){
                    curJ = j+jj;
                    if(curJ<0 || curJ>=dim){
                        continue;
                    }
                    curI = i+ii;
                    if(curI<0 || curI>=dim){
                        continue;
                    }
                    ps.red   += src[RIDX(curI, curJ, dim)].red *   kernel[ii+2][jj+2];
                    ps.green += src[RIDX(curI, curJ, dim)].green * kernel[ii+2][jj+2];
                    ps.blue  += src[RIDX(curI, curJ, dim)].blue *  kernel[ii+2][jj+2];
                    ps.weight += kernel[ii+2][jj+2];
                }
            }
            dst[RIDX(i,j,dim)].red   = (unsigned short)(ps.red/ps.weight);
            dst[RIDX(i,j,dim)].green = (unsigned short)(ps.green/ps.weight);
            dst[RIDX(i,j,dim)].blue  = (unsigned short)(ps.blue/ps.weight);
        }
    }
}

我的内核是

//emboss top-right kernel
Kernel emboss_tr_kernel = 
{
    {0.0,  -1.0, -1.0,   -1.0,  -1.0},
    {1.0,   0.0, -4.0,  -16.0,  -1.0},
    {1.0,   4.0,  1.0,   -4.0,  -1.0},
    {1.0,  16.0,  4.0,    0.0,  -1.0},
    {1.0,   1.0,  1.0,    1.0,   0.0}
};

【问题讨论】:

    标签: c optimization convolution


    【解决方案1】:

    评论太大了,所以我发布作为答案。

    实际上,通过更改两个外部循环的顺序,您可能会获得最大的加速。您的内部循环遍历i。但是src[RIDX(curI, curJ, dim)] 扩展为((curI)*(dim)+(curJ))。因此,每 2 个连续的 curIs 在内存中彼此相距 dim 像素。因此,您将以自己的方式生成大量缓存未命中——遍历列。 而是迭代行。

    接下来考虑将仅与jj 循环相关的代码段移动到它的循环而不是最内层循环。如:

    for (jj = -2; jj <= 2; jj++){
        curJ = j+jj;
        if(curJ<0 || curJ>=dim){
            continue;
        }
    

    你也可以使用

    for(curJ=max(0, j-2); curJ <= min(dim-1, j+2); ++curJ)
    

    接下来看看你是否可以分解你的内核线性可分的内核。如果是这样,请重写代码以利用它。 (已编辑:我认为这是不可能的,因为您的矩阵已满秩。)

    然后最后考虑是否可以并行化代码。但请注意不要以会导致多个线程共享同一缓存行的方式这样做。 http://www.drdobbs.com/parallel/eliminate-false-sharing/217500206

    【讨论】:

      【解决方案2】:

      有多种优化机会。

      通用优化

      • 从内部循环中删除条件
        • 选项 1 -- 为所有 9 个段制作不同的内循环
        • 选项 2 -- 将 src 缓冲区复制到具有预先清除边界的临时缓冲区
      • 选择选项 2 后,在单平面中解交织数据,例如

        rows -2, -1: all zeroes
        rows 0..dim-1: [0 0][green][0 0 0 0][red][0 0 0 0][blue][0 0]
        rows dim, dim+1: all zeroes
        
        • 这为自动 SIMD 优化提供了更好的机会
        • 垂直平面化也可以...
      • “sum+=weight”的强度降低是恒定的(是不是误为1?)——不需要每次都计算

      • 强度将内循环中的src指针计算减少到(src += dim_padded;)

      • 使用内部函数进行手动 SIMD 矢量化

        • 填充宽度为 4 的倍数 (+4) 并丢弃不需要的结果
        • 每 4 个样本加载 5 个寄存器,然后与内核的 4 个移位版本进行卷积
        • 从寄存器操作只会放宽内存带宽和缓存未命中率

      内核特定优化

      1) 使用常量 +-{1,4,16} 作为整数写出表达式

      • 编译器可以使用最优指令进行乘法
      • 没有转换为浮动和返回
      • 先加/减,后乘(每个内核只有 2 次乘法)

      2) 内核在二维中运行了与附近单元共享总和的单元:

        [0 h h h h]
        [         ]
        [         ]
        [         ]
      [0[h h h h] ]   <-- here the sum `h+h+h+h` is shared between the two
      [V - d - v]         positions.
      [V D - d v]
      [V - D - v]
      [H H H H -]
      

      这里Sum(v)[row][column_X] == -Sum(v)[row][column_X + 4]Sum(H)[row][column] == -Sum(h)[row + 4][column - 1]

      因此,通过将 3 个和 4 个元素的垂直和水平总和计算到临时数组中,可以稍微简化计算(和内存带宽)。

      那么内部循环只需要访问 11 个项目。类似的情况可以用于对角线和d = -D,这可能会导致投资收益递减。

      【讨论】:

        猜你喜欢
        • 2018-08-10
        • 2020-08-13
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-06-07
        • 2018-12-09
        相关资源
        最近更新 更多