.NET图表控件并行性能

dew*_*ald 6 .net parallel-processing performance charts controls

我正在使用.NET 4.0 Beta 2附带的.NET Chart Control库在后台线程上创建图像并将其保存到磁盘.我没有在屏幕上显示图表,只是创建一个图表,将其保存到磁盘并销毁它.像这样的东西:

public void GeneratePlot(IList<DataPoint> series, Stream outputStream) {
    using (var ch = new Chart()) {
        ch.ChartAreas.Add(new ChartArea());
        var s = new Series();
        foreach (var pnt in series) s.Points.Add(pnt);
        ch.Series.Add(s);
        ch.SaveImage(outputStream, ChartImageFormat.Png);
    }
}
Run Code Online (Sandbox Code Playgroud)

创建和保存每个图表大约需要300到400毫秒.我可能有数百个图表要创建,所以我想我会用它Parallel.For()来并行化这些任务.我有一个8核机器,但是,当我尝试一次创建4个图表时,我的图表创建/保存时间增加到800到1400毫秒,几乎所有这些都消耗了Chart.SaveImage.

我认为这可能是磁盘I/O的限制,所以为了测试我将最后一行更改为:

ch.SaveImage(Stream.Null, ChartImageFormat.Png);
Run Code Online (Sandbox Code Playgroud)

即使写入空流,性能仍然大致相同(800 - 1400毫秒).

我不应该在与这个库并行的后台线程上创建图像,或者我做错了什么?

谢谢

编辑:添加完整代码示例

只需更改传递给的标志CreateCharts()即可测试并行与串行.

using System;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms.DataVisualization.Charting;

namespace ConsoleChartTest
{
    class Program
    {
        public static void GeneratePlot(IEnumerable<DataPoint> series, Stream outputStream)
        {
            long beginTime = Environment.TickCount;

            using (var ch = new Chart())
            {
                ch.ChartAreas.Add(new ChartArea());
                var s = new Series();
                foreach (var pnt in series)
                    s.Points.Add(pnt);
                ch.Series.Add(s);

                long endTime = Environment.TickCount;
                long createTime = endTime - beginTime;

                beginTime = Environment.TickCount;
                ch.SaveImage(outputStream, ChartImageFormat.Png);
                endTime = Environment.TickCount;
                long saveTime = endTime - beginTime;

                Console.WriteLine("Thread Id: {0,2}  Create Time: {1,3}  Save Time: {2,3}",
                    Thread.CurrentThread.ManagedThreadId, createTime, saveTime);
            }
        }

        public static void CreateCharts(bool parallel)
        {
            var data = new DataPoint[20000];
            for (int i = 0; i < data.Length; i++)
            {
                data[i] = new DataPoint(i, i);
            }

            if (parallel)
            {
                Parallel.For(0, 10, (i) => GeneratePlot(data, Stream.Null));
            }
            else
            {
                for (int i = 0; i < 10; i++)
                    GeneratePlot(data, Stream.Null);
            }
        }

        static void Main(string[] args)
        {
            Console.WriteLine("Main Thread Id: {0,2}", Thread.CurrentThread.ManagedThreadId);

            long beginTime = Environment.TickCount;
            CreateCharts(false);
            long endTime = Environment.TickCount;
            Console.WriteLine("Total Time: {0}", endTime - beginTime);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

Nic*_*ver 3

您遇到了System.Drawing命名空间问题。其中有一些严重的线程锁定,这将序列化某些任务。直到你调用Chart.SaveImage()它才真正渲染图像,这就是你一直在吃的东西。

如果您稍微更改一下测试程序,您可以看到并行化正在发生,但它受到图形绘制代码内部锁定的严重阻碍。

在这里玩弄count = 50主方法...同时看到两个输出有助于我认为,您可以看到并行输出始终更快,尽管由于绘图命名空间中的锁定,它不会线性缩放:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms.DataVisualization.Charting;

namespace ConsoleChartTest
{
  class Program
  {
    static void Main(string[] args)
    {
      var count = 50;
      Console.WriteLine("Serial Test Start, Count: {0}");
      Console.WriteLine("Main Thread Id: {0,2}", Thread.CurrentThread.ManagedThreadId);

      var sw = new Stopwatch();
      sw.Start();
      CreateCharts(count, false);
      sw.Stop();
      Console.WriteLine("Total Serial Time: {0}ms", sw.ElapsedMilliseconds);

      Console.WriteLine("Parallel Test Start");
      Console.WriteLine("Main Thread Id: {0,2}", Thread.CurrentThread.ManagedThreadId);

      sw.Restart();
      CreateCharts(count, true);
      sw.Stop();
      Console.WriteLine("Total Parallel Time: {0}ms", sw.ElapsedMilliseconds);
    }

    public static void GeneratePlot(IEnumerable<DataPoint> series, Stream outputStream)
    {
      var sw = new Stopwatch();
      sw.Start();

        var ch = new Chart();
        ch.ChartAreas.Add(new ChartArea());
        var s = new Series();
        foreach(var pnt in series) s.Points.Add(pnt);
        ch.Series.Add(s);

        sw.Stop();
        long createTime = sw.ElapsedMilliseconds;
        sw.Restart();

        ch.SaveImage(outputStream, ChartImageFormat.Png);
        sw.Stop();

        Console.WriteLine("Thread Id: {0,2}  Create Time: {1,3}ms  Save Time: {2,3}ms",
            Thread.CurrentThread.ManagedThreadId, createTime, sw.ElapsedMilliseconds);
    }

    public static void CreateCharts(int count, bool parallel)
    {
      var data = new DataPoint[20000];
      if (parallel)
      {
        Parallel.For(0, data.Length, (i) => data[i] = new DataPoint(i, i));
        Parallel.For(0, count, (i) => GeneratePlot(data, Stream.Null));
      }
      else
      {
        for (int i = 0; i < data.Length; i++)
          data[i] = new DataPoint(i, i);
        for (int i = 0; i < count; i++)
          GeneratePlot(data, Stream.Null);
      }
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

锁定的是Chart.SaveImage()-> ChartImage.GetImage()->ChartPicture.Paint()