【问题标题】:C# delegate multithreading slower than single threaded?C#委托多线程比单线程慢?
【发布时间】:2017-05-07 22:18:42
【问题描述】:

我目前正在编写一个 C# 应用程序来演示并行计算相对于单线程应用程序的加速。我的情况是图像的中值模糊。但是让更多线程工作会显着降低应用程序的速度(单线程 60 秒 vs 多线程 75 秒)。鉴于我目前的方法,我不知道如何改进多线程处理。提前对这篇文章中的长代码表示抱歉。

我目前的做法:

首先,我计算每个线程需要处理多少像素才能均匀工作,DateTime计算是知道单线程传递了多少时间,多线程传递了多少时间:

public void blurImage(int cores)
    {
        _startTotal = DateTime.Now;

        int numberOfPixels = _originalImage.Width * _originalImage.Height;

        if (cores>=numberOfPixels)
        {
            for (int i = 0; i < numberOfPixels; i++)
            {
                startThread(0, numberOfPixels);
            }
        }
        else
        {
            int pixelsPerThread = numberOfPixels / cores;

            int threshold = numberOfPixels - (pixelsPerThread * cores);

            startThread(0, pixelsPerThread + threshold);

            for (int i = 1; i < cores; i++)
            {
                int startPixel = i * pixelsPerThread + threshold;
                startThread(startPixel, startPixel + pixelsPerThread);
            }
        }

        _SeqTime = DateTime.Now.Subtract(_startTotal);
    }

startThread 方法启动一个线程并将结果保存到一个特殊的类对象中,以便可以将其全部合并到一个图像中,我在每个线程中传递输入图像的副本。

private void startThread(int startPixel, int numberOfPixels)
    {
        BlurOperation operation = new BlurOperation(blurPixels);
        _operations.Add(operation);

        BlurResult result = new BlurResult();

        operation.BeginInvoke((Bitmap)_processedImage.Clone(), startPixel, numberOfPixels, _windowSize, result, operation, new AsyncCallback(finish), result);
    }

每个线程都会模糊他们的像素集并将结果保存到一个新的颜色列表中,结果保存到结果对象以及 startpixel 和当前操作中,因此程序知道所有线程何时完成:

private void blurPixels(Bitmap bitmap, int startPixel, int endPixel, int window, BlurResult result, BlurOperation operation)
    {

        List<Color> colors = new List<Color>();

        for (int i = startPixel; i < endPixel; i++)
        {

            int x = i % bitmap.Width;
            int y = i / bitmap.Width;

            colors.Add(PixelBlurrer.ShadePixel(x, y, bitmap, window));
        }

        result._pixels = colors;
        result._startPixel = startPixel;
        result._operation = operation;
    }

PixelBlurrer 计算每个颜色通道的中值并返回:

public static Color ShadePixel(int x, int y, Bitmap image, int window)
    {
        List<byte> red = new List<byte>();
        List<byte> green = new List<byte>();
        List<byte> blue = new List<byte>();

        int xBegin = Math.Max(x - window, 0);
        int yBegin = Math.Max(y - window, 0);

        int xEnd = Math.Min(x + window, image.Width - 1);
        int yEnd = Math.Min(y + window, image.Height - 1);

        for (int tx = xBegin; tx < xEnd; tx++)
        {
            for (int ty = yBegin; ty < yEnd; ty++)
            {
                Color c = image.GetPixel(tx, ty);

                red.Add(c.R);
                green.Add(c.G);
                blue.Add(c.B);
            }
        }

        red.Sort();
        green.Sort();
        blue.Sort();

        Color output = Color.FromArgb(red[red.Count / 2], green[green.Count / 2], blue[blue.Count / 2]);
        return output;
    }

在回调中,我们返回 GUI 线程并将所有像素合并到生成的图像中。最后一个事件被称为告诉我的表单该过程已完成:

private void finish(IAsyncResult iar)
    {
        Application.Current.Dispatcher.BeginInvoke(new AsyncCallback(update), iar);
    }

    private void update(IAsyncResult iar)
    {
        BlurResult result = (BlurResult)iar.AsyncState;
        updateImage(result._pixels, result._startPixel, result._operation);
    }

    private void updateImage(List<Color> colors, int startPixel, BlurOperation operation)
    {
        DateTime updateTime = DateTime.Now;

        _operations.Remove(operation);

        int end = startPixel + colors.Count;

        for (int i = startPixel; i < end; i++)
        {
            int x = i % _processedImage.Width;
            int y = i / _processedImage.Width;

            _processedImage.SetPixel(x, y, colors[i - startPixel]);
        }

        if (_operations.Count==0)
        {
            done(this, null);
        }

        _SeqTime += DateTime.Now.Subtract(updateTime);
    }

有什么想法吗?我尝试使用 Parallel.For 而不是委托,但这使情况变得更糟。有没有办法通过多线程加速中值模糊或者这是一个失败的案例?

【问题讨论】:

  • 尝试使用Stopwatch 类而不是DateTime 来进行准确的性能测量。 stackoverflow.com/questions/969290/…
  • 仅仅因为你可以在一个任务中抛出更多线程,并不意味着你应该。不用说,如果您的线程试图访问会产生不利影响的公共资源。如果您启动了许多线程,您将有管理线程池的开销,这个列表还在继续。 stackoverflow.com/questions/29960436/…stackoverflow.com/questions/12390468/…
  • 因为每个线程都使用它自己的对象,它们不会访问相同的内存。因此,唯一使用的公共资源是生成的图像。当我测量该时间并将其从总时间中减去时,该开销不存在于 60 秒与 75 秒之间。我没想到完美的 8 倍加速,但我确实期望加速。这怎么能慢?
  • 从您发布的代码中,尚不清楚作业在什么时候被委派给辅助线程(我想这发生在BlurOperation.BeginInvoke()。您可以发布该代码吗?
  • 'BlurOperation.BeginInvoke()' 只是一个启动 'blurPixels()' void 的委托。

标签: c# .net multithreading image-processing delegates


【解决方案1】:

经过一番思考,我发现我的逻辑是可靠的,但我没有向每个线程发送深层副本。在 StartThread 中更改此行后:

operation.BeginInvoke((Bitmap)_processedImage.Clone(), startPixel, numberOfPixels, _windowSize, result, operation, new AsyncCallback(finish), result);

到这里:

operation.BeginInvoke(new Bitmap(_processedImage), startPixel, numberOfPixels, _windowSize, result, operation, new AsyncCallback(finish), result);

我可以看到加速多线程

【讨论】:

    猜你喜欢
    • 2020-08-15
    • 1970-01-01
    • 2012-09-05
    • 1970-01-01
    • 1970-01-01
    • 2020-05-16
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多