【问题标题】:How are GDI+ functions so fast?GDI+ 功能怎么这么快?
【发布时间】:2016-03-19 20:32:14
【问题描述】:

我正在尝试重新创建非常简单的 GDI+ 功能,例如缩放和旋转图像。原因是一些 GDI 函数不能在多个线程上完成(我发现了一个使用进程的解决方法,但不想进入那个),并且在一个线程上处理数千个图像几乎没有削减它。 另外我的图像是灰度的,所以自定义函数只需要担心一个值而不是 4。

无论我尝试重新创建什么样的函数,即使经过高度优化,它总是慢几倍,尽管与 GDI 所做的相比已大大简化(我正在操作一维字节数组,每个字节一个字节)像素)

我想也许我旋转每个点的方式可能会有所不同,所以我将它完全取出,基本上有一个函数可以遍历每个像素并将其设置为已经存在的状态,这只是粗略的与 GDI 的速度相关,即使 GDI 正在执行实际旋转并且每个像素更改 4 个不同的值。

是什么让这成为可能?有没有办法使用你自己的函数来匹配它?

【问题讨论】:

  • Graphics Device Interface 应该很快。它是用原生 C/C++ 编写的,它甚至可以使用图形适配器的硬件功能来绘制例如线。这将比 C# 中的每像素迭代快得多。如果您也学习如何使用这些功能(例如托管 DirectX),您可以尝试获得几乎相同的性能。
  • Frobot 的好问题。希望知道的人回答。
  • 位图操作具有基本的 O(n^2) 复杂性。这上升得非常快,一个适度的 n=1000 已经是一百万次操作。只有蛮力可以帮助保持哦足够小。 GDI+ 肯定会偷工减料,例如它不是像素完美的。并且很可能使用手动调整的 SIMD 代码。只有微软知道,来源不公开。
  • `我以为GDI根本不接触GPU`-not true
  • 这个问题有点类似于说:“我知道法拉利几十年来一直在生产跑车,并且在研发上花费了数百万美元,但我在我的车库,它的速度远没有那么快。我做错了什么?”

标签: c# gdi+ gdi


【解决方案1】:

GDI+ 代码是用 C/C++ 编写的,甚至可能部分是用汇编语言编写的。一些 GDI+ 调用可能会使用 GDI,这是一种旧的且经过良好优化的 API。即使你知道所有的像素操作技巧,你也会发现很难匹配性能。

【讨论】:

  • 是的。基本上,它通过在后台调用管理较少、更高效的 API 和操作系统操作来“作弊”。 List<T>.Sort() 做同样的事情。 ;)
  • 是的,我认为它归结为由专业人员使用一些技巧构建的高度优化的 API,并且不受管理。我仍然可以使用跨多个线程的自定义函数来超越它的速度,只是没有预期的那么多。感谢大家的意见
  • @Haney:出于好奇,List<T>.Sort() 在呼唤什么? - 我不知道原生 Windows API 提供了排序功能。
  • @500-InternalServerError 慢跑我的记忆......自从我研究它已经有几年了,但如果我记得正确的话,它调用了一个 C 库,它使用指针进行快速排序
  • 我用我的代码添加了一个答案。如果有人知道进一步改进的方法,请分享
【解决方案2】:

我正在添加我自己的答案以及我的代码,以帮助其他可能希望这样做的人。

从指针的组合和使用正弦和余弦的近似值而不是调用外部函数进行旋转,我已经非常接近达到 GDI 速度。根本没有调用外部函数。

它仍然比 GDI 多花费大约 50% 的时间,但我之前的实现比 GDI 花费了 10 倍以上的时间。而且当你考虑多线程时,这种方法可以比 GDI 快 10 倍。这个函数可以在我的机器上在 3 毫秒内旋转一张 300x400 的图片。

请记住,这是针对灰度图像的,输入数组中的每个字节代表一个像素。 如果您有任何想法让它更快,请分享!

private unsafe byte[] rotate(byte[] input, int inputWidth, int inputHeight, int cx, int cy, double angle)
    {
        byte[] result = new byte[input.Length];

        int
            tx, ty, ix, iy, x1, y1;
        double
            px, py, fx, fy, sin, cos, v;
        byte a, b;

        //Approximate Sine and Cosine of the angle
        if (angle < 0)
            sin = 1.27323954 * angle + 0.405284735 * angle * angle;
        else
            sin = 1.27323954 * angle - 0.405284735 * angle * angle;
        angle += 1.57079632;
        if (angle > 3.14159265)
            angle -= 6.28318531;
        if (angle < 0)
            cos = 1.27323954 * angle + 0.405284735 * angle * angle;
        else
            cos = 1.27323954 * angle - 0.405284735 * angle * angle;
        angle -= 1.57079632;


        fixed (byte* pInput = input, pResult = result)
        {
            byte* pi = pInput;
            byte* pr = pResult;

            for (int x = 0; x < inputWidth; x++)
                for (int y = 0; y < inputHeight; y++)
                {
                    tx = x - cx;
                    ty = y - cy;
                    px = tx * cos - ty * sin + cx;
                    py = tx * sin + ty * cos + cy;
                    ix = (int)px;
                    iy = (int)py;
                    fx = px - ix;
                    fy = py - iy;

                    if (ix < inputWidth && iy < inputHeight && ix >= 0 && iy >= 0)
                    {
                        //keep in array bounds
                        x1 = ix + 1;
                        y1 = iy + 1;
                        if (x1 >= inputWidth)
                            x1 = ix;
                        if (y1 >= inputHeight)
                            y1 = iy;

                        //bilinear interpolation using pointers
                        a = *(pInput + (iy * inputWidth + ix));
                        b = *(pInput + (y1 * inputWidth + ix));
                        v = a + ((*(pInput + (iy * inputWidth + x1)) - a) * fx);
                        pr = (pResult + (y * inputWidth + x));
                        *pr = (byte)(v + (((b + ((*(pInput + (y1 * inputWidth + x1)) - b) * fx)) - v) * fy));
                    }
                }
        }

        return result;
    }

【讨论】:

  • 我看到您可以做一些非常小的调整,例如将 tx = x - cx; 和两个相关术语 tx * costx * sin 移出内部循环嵌套,但后者需要更多临时工,所以你必须测试这样的事情是否值得。我还想知道在您的if 中切换到非短路布尔评估是否会带来轻微的提升。但总的来说,我认为您已经接近在这个级别上可以实现的目标(除非有一种我不知道的完全不同的方法)。
  • 您可以尝试另一件事,使用transformation matrix 将旋转应用于图像。您还可以使用 NuGet 包System.Numerics.Vectors 获取某些 Matrix 方法的硬件加速版本,以使其更快。
猜你喜欢
  • 2010-09-13
  • 1970-01-01
  • 2013-02-15
  • 2018-12-19
  • 2020-12-19
  • 2011-06-02
  • 2012-03-18
  • 2014-02-21
  • 1970-01-01
相关资源
最近更新 更多