如何优化图像像素化程序

时间:2019-04-16 19:23:31

标签: c performance optimization profiling cpu

我已经编写了一些串行代码,在使用OpenMP对其进行并行化之前,我想尽可能地对其进行优化。该程序通过遍历4x4单元(变量c)中的像素数据来读取PPM文件,然后找到这4x4单元中每个像素的平均RGB值,最后通过输出每个4x4单元的平均颜色值。这会产生一种马赛克/像素化效果。

对我的代码进行性能分析后,主要瓶颈是fscanffprintf。我忽略了读写磁盘的执行时间,因此这两个功能无关紧要。

到目前为止,我一直在努力进行优化:

  • 循环阻塞:代码中有两个嵌套的FOR循环,它们具有完全相同的循环条件。但是,第二组嵌套的FOR循环要求将计算平均RGB值所需的函数保持在该特定组之外。然后需要在第三组嵌套的FOR循环(与第二组具有相同的循环条件)中使用平均RGB值计算。因此,尽管有相似之处,但我还是很难将第二套和第三套嵌套的FOR循环组合在一起。

  • 循环不变计算:我已尝试将某些操作尽可能地移到循环外,但是事实证明这很困难。

总结:如何优化该程序以尽可能减少执行时间?

我的代码:

typedef struct {                                //struct holding RGB type int
    int r, g, b;    //12 bytes
} pixelInt;
typedef struct {                                //struct holding RGB type unsigned char
    unsigned char r, g, b;  //3 bytes
} pixel;

int c = 4;             // Variable of 4x4 grids
int width, height;    //image variable declarations

//Raw 1 dimensional store of pixel data - will contain all the data for each pixel in the image
pixel *data = (pixel *)calloc(width * height, sizeof(pixelInt));

//Loop through entire input image 
        for (int yy = 0; yy < height; yy += c)
        {
            for (int xx = 0; xx < width; xx += c)
            {
                //the total colour of cell of size 'c'
                pixelInt cell_tot = { 0, 0, 0 };        //zero initialize struct containing mosaic cell pixel totals.

                unsigned int counter = 0; //the counter for how many pixels are in a 4x4 cell

                int bx = xx + c;  //used in loop conditions
                int by = yy + c;

                // Store each color from the cell into cell_tot struct
                for (int y = yy; y < by; y++)
                {
                    for (int x = xx; x < bx; x++)
                    {
                        unsigned int index_1d = x + y * width;      //calculate 1d index from x-index (x), y-index(y) and width;

                        unsigned char r, g, b; //maximum vales are 255, i.e. unsigned char data type                    

                        fscanf(f, "%hhu %hhu %hhu", &r, &g, &b); //%hhu is unsigned char specifier

                        //store the pixel value into data array
                        data[index_1d].r = r;
                        data[index_1d].g = g;
                        data[index_1d].b = b;

                        counter++; //increment counter

                        //average pixel color of cell
                        cell_tot.r += r;
                        cell_tot.g += g;
                        cell_tot.b += b;

                    }
                }

                //average colour of cell found by dividing cell total by loop counter 
                pixel cell_average;
                cell_average.r = cell_tot.r / counter;
                cell_average.g = cell_tot.g / counter;
                cell_average.b = cell_tot.b / counter;

                //Loop through the new image in cells of size c 
                for (int y = yy; y < by; y++)
                {
                    for (int x = xx; x < bx; x++)
                    {
                        unsigned int index_1d = x + y * width;      //calculate 1d index from x-index (x), y-index(y) and width;

                        //Assign average cell value to the pixels in the cell
                        data[index_1d].r = cell_average.r;
                        data[index_1d].g = cell_average.g;
                        data[index_1d].b = cell_average.b;

                        //Output the average colour value for the image  
                        fprintf(f_output, "%hhu %hhu %hhu \t", data[index_1d].r, data[index_1d].g, data[index_1d].b);
                    }
                    fprintf(f_output, "\n");    //Prints new line 
                }
            }
        } 

1 个答案:

答案 0 :(得分:1)

在我的计算机上的1024x1024图像上,您的代码在0.325s中执行。以下代码在0.182s中执行:

unsigned w = width/c, h = height/c;
unsigned *accum = (unsigned*)malloc(3*sizeof(unsigned)*w);
char *line = (char*)malloc(12*w);
unsigned denom = c*c;

//Loop through entire input image 
for (int yy = 0; yy < h; ++yy)
{
    memset(accum, 0, 3*sizeof(unsigned)*w);

    // read and accumulate c lines
    for(int y = 0; y < c; ++y)
    {
        for (int xx = 0; xx < w; ++xx)
        {
            for (int x = 0; x < c; ++x)
            {
                unsigned char r, g, b;
                fscanf(f, "%hhu %hhu %hhu", &r, &g, &b);
                accum[3*xx+0] += r;
                accum[3*xx+1] += g;
                accum[3*xx+2] += b;
            }
        }
    }

    // format a line
    for(int xx = 0; xx < w; ++xx)
    {
        char *cell = line + 12*c*xx; 
        snprintf(cell, 12, "%3u%4u%4u", accum[3*xx]/denom, accum[3*xx+1]/denom, accum[3*xx+2]/denom);
        cell[11] = '\t';
        for(int x = 1; x < c; ++x)
            memcpy(cell + 12*x, cell, 12);
    }

    // write it out times c
    line[12*w-1] = '\n';
    for(int y = 0; y < c; ++y)
        fwrite(line, 12*w, 1, f_output);
}

此处的技巧是仅将平均值格式化一次,然后复制格式化的字符串。同样,通过一次执行一行操作,我有更多机会利用内存缓存。

要超出此范围,您将需要重新实现fscanf以更快地解析整数。