Why is there no increase in speed when scaling images using Parallel.For?

I can’t get a speed boost while scaling multiple bitmaps in parallel.

Read the following code demonstrating the problem:

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

I get the following results on my system (recent quad i7):

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

This is an unexpected result for two reasons:

  • Images are quite large (24 megapixels). The overhead of starting multiple threads should not have a significant impact.
  • Image scaling, of course, is associated with the processor.

To make sure that I have replaced ImageScaler()in the code above the following algorithm for scaling the main image:

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

With this code, I get the following results:

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

3,6 , .

, ? System.Drawing, ?

" ":

, Multithreading System.Windows.Graphics . , . , .

, ( GDI) .

, (!): GDI + .net

+4

Source: https://habr.com/ru/post/1689867/


All Articles