.NET Chart Control Параллельное выполнение - PullRequest
6 голосов
/ 15 января 2010

Я использую библиотеку .NET Chart Control, поставляемую с .NET 4.0 Beta 2, для создания и сохранения изображений на диск в фоновом потоке. Однако я не показываю диаграмму на экране, просто создаю диаграмму, сохраняю ее на диск и уничтожаю. Примерно так:

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.

Я подумал, что это может быть ограничение дискового ввода-вывода, поэтому для проверки я изменил последнюю строку на:

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 ]

3 голосов
/ 06 февраля 2010

У вас проблемы с пространством имен 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() -> ChartImage.GetImage() -> ChartPicture.Paint()

0 голосов
/ 04 февраля 2010

Помните, у вас есть Hyper-Threading и действительно ядра. Вы должны быть осторожны с этим, гиперпоточность не имеет такую ​​же производительность, как ядро.

Другое дело, что для параллельной работы хорошо работать, когда вы создаете поток пула, вы должны установить максимальное количество потоков, например

MaxThreads = Cores - 2;

Когда я говорю о ядрах, читайте ядра, а не гиперпоточные ядра.

1 - ОС 1 - Основное применение X - ядра для обработки.

Если вы создадите слишком много потоков, вы потеряете производительность из-за совпадения в процессоре.

Создание изображений в формате Jpeg или Png - это еще один хороший момент, так как при сохранении изображения вы будете тратить меньше времени на HD. Позаботьтесь о качестве JPEG и PNG тоже, потому что, если он на 100%, он может быть большим.

Другой важный момент. У вас будет совпадение на HD, потому что на нем будет много потоков, создающих архивы. Что вы можете с этим поделать? Это действительно труднее решить, потому что у нас нет параллельных hds. Таким образом, вы можете создать место для отправки буферов изображений, как и другие потоки, которые ничего не обрабатывают, просто находясь там, получая буфер изображений и сохраняя его, например, во внутреннем списке. И через 5 часов 5 секунд (или некоторые условия, которые, по вашему мнению, лучше), он начинает писать картинки на HD. Таким образом, у вас будет поток, работающий на HD без одновременного совпадения, а другие потоки просто для обработки изображений.

в.

0 голосов
/ 06 февраля 2010

Если бы мне пришлось угадывать, я бы сказал, что, похоже, код внутри SaveImage защищен CriticalSection или Lock, что позволяет одновременно запускать только один поток в частях этого кода.

Итак, вы могли бы быть правы насчет того, что вам не разрешено использовать фоновые потоки, но я думаю, что более вероятно, что вам не разрешено иметь более 1 потока, работающего внутри SaveImage одновременно. Документация по этой функции довольно скудная, но время ее появления весьма показательно. 4 графика занимают примерно 4 раза больше, чем 1 график.

Если вы сохраняете только 1 график с помощью фонового потока - он идет на полной скорости?

0 голосов
/ 30 января 2010

Может быть, вы могли бы сохранить изображение как BMP, что заняло бы больше места на диске, но сократило бы объем обработки, который ему потребовалось бы сделать. Вы должны попробовать различные форматы изображений, чтобы увидеть, вызывает ли сжатие проблемы. Если это так, вы можете захотеть выделить некоторые другие потоки, чтобы выполнить сжатие после.

...