2010-01-15 3 views
6

Я использую библиотеку .NET Chart Control, которая поставляется с .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.

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

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

Даже запись в нулевой поток производительность еще примерно такой же (800 - 1400 мс).

Я не должен создавать изображения на фоне потоков параллельно с этой библиотекой, или я делаю что-то неправильно?

Благодаря

EDIT: Добавлен полный Пример кода

Просто изменить флаг передается 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); 
     } 
    } 
} 
+0

Юмор нас - вы можете разместить полный код, в том числе, где вы используете 'Parallel.For'? А также дать нам некоторое представление о том, как вы применяете этот код, откуда поступают цифры? Как выглядит использование ЦП во время бенчмаркинга? – Aaronaught

+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() ->ChartImage.GetImage() ->ChartPicture.Paint()

+0

Есть ли способ, которым вы знаете, чтобы обойти это узкое место? – dewald

+0

@ dewald - Непосредственно, хотя могут быть и сторонние альтернативы, которые не используют 'System.Drawing' (я думаю, что SoftwareFX использует свой собственный движок, но это не дешево). Мы можем надеяться, что они улучшат потоковую обработку в 4.0 RC/RTM, но это вряд ли возможно.Извините, ответ отстой, но из моего представления это похоже на то, что пространство имен Drawing осталось в темном возрасте. –

0

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

+0

Это не отвечает на вопрос, почему это не похоже на параллель. – Dykam

+0

Он может идти параллельно, но если компьютер слишком много переключается на задачу, тогда все может замедлить работу. Возможно, PNG-кодер уже работает параллельно под капотом, и, пытаясь запустить параллельные экземпляры, он создает слишком много потоков, что слишком сильно переводит ваш компьютер в контекстный переключатель. Как выглядит ваше использование процессора при работе с 1 потоком по сравнению со многими потоками? Это уже на 100% с 1 нитью? – Kibbee

+0

Я пробовал BMP (наряду со всеми другими форматами), но это не повлияло на параллельную производительность. – dewald

0

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

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

MaxThreads = Ядра - 2;

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

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

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

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

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

at.

+0

@SaCi - Это не проблема HD, поскольку я тестирую ее, записывая Stream.Null, поэтому она никогда не касается HD. Я попытался уменьшить максимальное количество потоков до 4 (поскольку я нахожусь на 8-ядерном компьютере), но это, похоже, не помогло. – dewald

0

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

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

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

Смежные вопросы