优化用于卷积图像的代码

时间:2017-11-12 04:04:11

标签: c optimization convolution

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

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

2 个答案:

答案 0 :(得分:0)

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

实际上,通过更改两个外循环的顺序,您可能获得最大的加速。你的内循环遍历i。但src[RIDX(curI, curJ, dim)]扩展为((curI)*(dim)+(curJ))。因此,每2个连续curI s在内存中彼此相距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

答案 1 :(得分:0)

有几种优化机会。

通用优化

  • 从内循环中移除条件
    • 选项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优化
    • 垂直平面化也有效......
  • 力量减少&#34;总和+ =体重&#34;是不变的(错误是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,这可能会降低投资收益。

相关问题