.NET图表控件并行性能

时间:2010-01-15 18:24:06

标签: .net performance controls parallel-processing charts

我正在使用.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);
    }
}

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

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

ch.SaveImage(Stream.Null, ChartImageFormat.Png);

即使写入空流,性能仍然大致相同(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);
        }
    }
}

4 个答案:

答案 0 :(得分: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);
      }
    }
  }
}

锁定的内容是Chart.SaveImage() - &gt; ChartImage.GetImage() - &gt;的 ChartPicture.Paint()

答案 1 :(得分:0)

也许您可以将图像保存为BMP,这将占用更多磁盘空间,但会减少需要执行的处理量。您应该尝试不同的图像格式,以查看压缩是否导致问题。如果是这样,你可能想要剥离其他一些线程来进行压缩。

答案 2 :(得分:0)

请记住,你有超线程和真正的核心。 你必须小心,超线程与核心没有相同的性能。

另外,使用并行执行的一个很好的方法是在创建池线程时,必须设置最大线程数,如

  

MaxThreads =核心 - 2;

当我说核心时,读取核心而不是超线程核心。

1 - 操作系统 1 - 主要应用 X - 要处理的核心。

如果您创建了太多线程,那么由于处理器的同时性,您将失去性能。

创建Jpeg或Png图像是另一个好处,这样您在保存图像时花费的时间就会减少。 注意jpeg和png的质量,因为如果它是100%它可能很大。

其他相关的观点。 你会在高清上有同意,因为很多线程都会创建档案。 你能做些什么呢? 这真的是一个难以解决的问题因为我们没有平行的hds。因此,您可以创建一个发送图像缓冲区的位置,就像其他线程一样,不处理任何内容,只是接收图像的缓冲区并将其存储在内部列表中。 在5 em 5秒内(或者你认为更好的某些条件),它开始在高清上写下图片。因此,您将有一个线程正在处理HD“没有”并发,而其他线程只是处理图像。

在。

答案 3 :(得分:0)

如果我不得不猜测,我会说SaveImage中的代码看起来像是由CriticalSection或Lock保护,它一次只允许一个线程在该代码的某些部分运行。

所以,你可能是正确的,不允许使用后台线程,但我认为你不可能一次在SaveImage中运行多于1个线程。关于此功能的文档非常稀疏,但时间非常具有启发性。 4张图表长度约为4倍,为1张图表。

如果使用后台线程只保存1个图表 - 是否全速运行?