C++ GDI+ 位图操作需要加快字节操作

C++ GDI+ bitmap manipulation needs speed up on byte operations

我在 C++ 中使用 GDI+ 来操作一些位图图像、更改颜色和调整图像大小。我的代码在某个特定点非常慢,我正在寻找一些可能的方法来加速 VS2013 Profiler

中突出显示的行
for (UINT y = 0; y < 3000; ++y)
    {
        //one scanline at a time because bitmaps are stored wrong way up
        byte* oRow = (byte*)bitmapData1.Scan0 + (y * bitmapData1.Stride);
        for (UINT x = 0; x < 4000; ++x)
        {
            //get grey value from 0.114*Blue + 0.299*Red + 0.587*Green
            byte grey = (oRow[x * 3] * .114) + (oRow[x * 3 + 1] * .587) + (oRow[x * 3 + 2] * .299); //THIS LINE IS THE HIGHLIGHTED ONE

            //rest of manipulation code
        }
    }

关于如何更好地处理这条算术线有什么方便的提示吗?它导致我的代码大幅减速

提前致谢!

优化在很大程度上取决于所使用的编译器和目标系统。但是有一些提示可能有用。避免乘法:

而不是:

byte grey = (oRow[x * 3] * .114) + (oRow[x * 3 + 1] * .587) + (oRow[x * 3 + 2] * .299); //THIS LINE IS THE HIGHLIGHTED ONE

使用...

 //get grey value from 0.114*Blue + 0.299*Red + 0.587*Green
 byte grey = (*oRow) * .114;
 oRow++;
 grey += (*oRow) * .587;
 oRow++;
 grey += (*oRow) * .299;
 oRow++;

你可以把指针的罪名放在同一行。我把它放在一个单独的行中以便更好地理解。

此外,您可以使用 table 而不是使用浮点数的乘法,这比算术运算更快。这取决于 CPU 和 table 大小,但您可以试一试:

// somwhere global or class attributes
byte tred[256];
byte tgreen[256];
byte tblue[256];

...启动时...

// Only init once at startup
// I am ignoring the warnings, you should not :-)
for(int i=0;i<255;i++)
{
  tred[i]=i*.114;
  tgreen[i]=i*.587;
  tblue[i]=i*.229;
}

...在循环中...

 byte grey = tred[*oRow];
 oRow++;
 grey += tgreen[*oRow];
 oRow++;
 grey += tblue[*oRow];
 oRow++;

还有。 255*255*255 不是很大。你可以建一个大table。因为这个 Table 会比通常的 CPU 缓存大,所以我给它的速度效率没有这么高。

  • 如建议的那样,您可以用整数进行数学运算,但您也可以尝试使用浮点数而不是双精度数(.114f 而不是 .114),这通常更快而且不需要精度.

  • 改为执行这样的循环,以节省指针数学运算。像这样创建一个临时指针不会有成本,因为编译器会理解你在做什么。

    for(UINT x = 0; x < 12000; x+=3) { byte* pVal = &oRow[x]; .... }

  • 这段代码也很容易线程化——编译器可以以各种方式自动为您完成;这是一个,并行用于: https://msdn.microsoft.com/en-us/library/dd728073.aspx 如果你有 4 个内核,那就是大约 4 倍的加速。

  • 还要确保检查发布版本与调试版本 - 在 运行 处于 release/optimized 模式之前,您不知道性能。

您可以预乘以下值:oRow[x * 3] * .114 并将它们放入数组中。 oRow[x*3] 有 256 个值,因此您可以轻松地创建 0->255 的 256 个值的数组 aMul1,并将其乘以 .144。然后使用 aMul1[oRow[x * 3]] 求乘积。其他组件也一样。

实际上,您甚至可以为 RGB 值创建这样的数组,即。你的像素是 888,所以你需要一个大小为 256*256*256 的数组,即 16777216 = ~16MB.Whether 这会加快你的进程,你必须用分析器检查自己。

总的来说,我发现更直接的指针管理、中间指令、更少的指令(在大多数 CPUs 上,这些天它们的成本都是一样的)和更少的内存获取 - 例如表格通常不是答案 - 是通常的最佳选择,无需直接组装。矢量化,尤其是显式矢量化也很有帮助,因为它可以转储函数的汇编并确认内部位符合您的期望。试试这个:

for (UINT y = 0; y < 3000; ++y)
{
    //one scanline at a time because bitmaps are stored wrong way up
    byte* oRow = (byte*)bitmapData1.Scan0 + (y * bitmapData1.Stride);
    byte *p = oRow;
    byte *pend = p + 4000 * 3;
    for(; p != pend; p+=3){
        const float grey = p[0] * .114f + p[1] * .587f + p[2] * .299f;
    }
    //alternatively with an autovectorizing compiler
    for(; p != pend; p+=3){
        #pragma unroll //or use a compiler option to unroll loops
        //make sure vectorization and relevant instruction sets are enabled - this is effectively a dot product so the following intrinsic fits the bill:
        //https://msdn.microsoft.com/en-us/library/bb514054.aspx
        //vector types or compiler intrinsics are more reliable often too... but get compiler specific or architecture dependent respectively.
        float grey = 0;
        const float w[3] = {.114f, .587f, .299f};
        for(int c = 0; c < 3; ++c){
            grey += w[c] * p[c];
        }
    }
}

考虑使用 OpenCL 并以您的 CPU 为目标,看看使用 CPU 特定优化和轻松多核解决问题的速度有多快 - OpenCL 为您提供了很好的解决方案,并提供内置矢量运算和点积。