使用Parallel.For缩放图像时为什么没有速度增加?

时间:2017-11-26 17:31:18

标签: c# performance parallel-processing

在并行缩放多个Bitmap时,我无法获得任何速度增益。

请查看以下代码以说明问题:

class Program
{
    static void Main(string[] args)
    {
        Bitmap[] images = Enumerable.Range(0, 8).Select(_ => new Bitmap(6000, 4000, PixelFormat.Format24bppRgb)).ToArray();
        Bitmap[] scaledImages = Enumerable.Range(0, 8).Select(_ => new Bitmap(600, 400, PixelFormat.Format24bppRgb)).ToArray();

        // Sequential image scaling:
        Stopwatch stopwatch = new Stopwatch();
        stopwatch.Start();
        for(int i = 0; i < images.Length; i++)
        {
            ImageScaler(images[i], scaledImages[i]);
        }
        stopwatch.Stop();
        Console.WriteLine($"Sequential scaling: {images.Length} images in {stopwatch.ElapsedMilliseconds} ms.");

        // Parallel image scaling:
        stopwatch.Restart();
        Parallel.For(0, images.Length, i => ImageScaler(images[i], scaledImages[i]));
        stopwatch.Stop();
        Console.WriteLine($"Parallel scaling: {images.Length} images in {stopwatch.ElapsedMilliseconds} ms.");

        Console.ReadKey();
    }

    private static void ImageScaler(Bitmap source, Bitmap destination)
    {
        using(Graphics g = Graphics.FromImage(destination))
        {
            g.InterpolationMode = InterpolationMode.HighQualityBicubic;
            g.DrawImage(source, 0, 0, destination.Width, destination.Height);
        }
    }
}

我的系统(最近的四核i7)得到以下结果:

Sequential scaling: 8 images in 1774 ms.
Parallel scaling: 8 images in 1792 ms.

这是一个意外的结果有两个原因:

  • 图像相当大(24万像素)。启动多个线程的开销不应产生重大影响。
  • 图像缩放绝对是CPU绑定操作。

为验证这一点,我通过以下基本图像缩放算法替换了上面代码中的ImageScaler()

private static unsafe void NaiveImageScaler(Bitmap src, Bitmap dst)
{
    BitmapData srcBd = src.LockBits(new Rectangle(0, 0, src.Width, src.Height), ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);
    BitmapData dstBd = dst.LockBits(new Rectangle(0, 0, dst.Width, dst.Height), ImageLockMode.WriteOnly, PixelFormat.Format24bppRgb);
    unsafe
    {
        byte* srcBgr = (byte*)srcBd.Scan0.ToPointer();
        byte* dstBgr = (byte*)dstBd.Scan0.ToPointer();
        for(int yd=0; yd<dst.Height; yd++)
        {
            for(int xd = 0; xd < dst.Width; xd++)
            {
                int bSum = 0, gSum = 0, rSum = 0;
                for(int ys = 10*yd; ys < 10*yd+10; ys++)
                {
                    for(int xs = 10*xd; xs < 10*xd+10; xs++)
                    {
                        bSum += srcBgr[ys * srcBd.Stride + 3 * xs];
                        gSum += srcBgr[ys * srcBd.Stride + 3 * xs + 1];
                        rSum += srcBgr[ys * srcBd.Stride + 3 * xs + 2];
                    }
                }
                dstBgr[yd * dstBd.Stride + 3 * xd] = (Byte)(bSum / 100);
                dstBgr[yd * dstBd.Stride + 3 * xd + 1] = (Byte)(bSum / 100);
                dstBgr[yd * dstBd.Stride + 3 * xd + 2] = (Byte)(bSum / 100);
            }
        }
    }
    dst.UnlockBits(dstBd);
    src.UnlockBits(srcBd);
}

使用此代码,我得到以下结果:

Sequential scaling: 8 images in 660 ms.
Parallel scaling: 8 images in 184 ms.

并行实现现在比顺序实现快3.6倍。

为什么两种方法之间的差异基本上完成了相同的工作? System.Drawing中是否存在阻止运行并行代码的限制?

关于&#34;标记为重复的评论&#34;:

我不认为Multithreading System.Windows.Graphics完全相同。提到的问题是关于绘图,这个是关于图像缩放。受试者是相关的,但并不完全相同。

我同意基本解释(GDI中的关键区域)适用于这两个问题。

我相信我发现了更接近我自己的问题(!): Parallelizing GDI+ Image Resizing .net

0 个答案:

没有答案