2010-01-15 7 views
6

Ich verwende die .NET Chart Control-Bibliothek, die mit .NET 4.0 Beta 2 kommt, um Bilder auf der Festplatte in einem Hintergrund-Thread zu erstellen und zu speichern. Ich zeige das Diagramm nicht auf dem Bildschirm, sondern erstelle einfach ein Diagramm, speichere es auf der Festplatte und zerstöre es. Etwas wie folgt aus:.NET Chart Control Parallel Performance

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); 
    } 
} 

Es wurde etwa 300 nehmen - 400 ms jedes Diagramm zu erstellen und zu speichern. Ich habe möglicherweise Hunderte von Diagrammen zu erstellen, also dachte ich, ich würde 10 verwenden, um diese Aufgaben zu parallelisieren. Ich habe einen 8-Kern-Rechner, aber wenn ich versuche, 4 Diagramme gleichzeitig zu erstellen, erhöht sich die Zeit zum Erstellen/Speichern meines Diagramms auf 800 bis 1400 ms, wobei fast alle von Chart.SaveImage verbraucht werden.

Ich dachte, das eine Begrenzung der Platte sein könnte, I/O, so zu prüfen, dass ich die letzte Zeile geändert:

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

Auch auf eine Null die Leistung streamen noch etwa gleich ist (800 - 1400 ms).

Soll ich nicht parallel zu dieser Bibliothek Bilder auf Hintergrundthreads erstellen oder mache ich etwas falsch?

Dank

EDIT: hinzugefügt Vollständiges Codebeispiel

einfach die Fahne CreateCharts() weitergegeben ändern im Vergleich zu seriellen parallel zu testen.

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

Humor uns - können Sie den vollständigen Code veröffentlichen, einschließlich, wenn Sie 'Parallel.For' verwenden? Und geben Sie uns auch eine Vorstellung davon, wie Sie diesen Code instrumentieren, woher die Zahlen kommen? Wie sieht die CPU-Auslastung während Ihres Benchmarks aus? – Aaronaught

+0

vielleicht ist das Problem in der Umwandlung, irgendwie können Sie mehr Code bekannt geben? –

Antwort

3

Probleme mit dem Namespace System.Drawing treten auf. Es gibt da einige schwere Threads, die bestimmte Aufgaben serialisieren. Nicht bis Sie Chart.SaveImage() anrufen, tut es tatsächlich render das Bild, das ist, was alle Ihre Zeit zu essen ist.

Wenn Sie Ihr Testprogramm ein wenig ändern, können Sie sehen, dass die Parallelisierung stattfindet, aber es wird durch die Sperre im Graphics-Zeichencode stark behindert.

Spielzeug herum mit der count = 50 in der Hauptmethode hier ... sehen beide Ausgänge zur gleichen Zeit hilft, denke ich, können Sie sehen, dass die parallele ist immer schneller, obwohl es nicht linear skalieren wegen der Verriegelung in der Zeichnungsnamespace:

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); 
     } 
    } 
    } 
} 

Was Perren ist Chart.SaveImage() ->ChartImage.GetImage() ->ChartPicture.Paint()

+0

Gibt es einen Weg, um diesen Engpass zu umgehen? – dewald

+0

@dewald - Nicht direkt, obwohl es einige Drittanbieteralternativen geben kann, die 'System.Drawing' nicht verwenden (ich denke, SoftwareFX verwendet eine eigene Engine, aber es ist nicht billig). Wir können hoffen, dass sie das Threading in 4.0 RC/RTM verbessern, aber das scheint nicht wahrscheinlich.Sorry, die Antwort ist schlecht, aber aus meiner Sicht ist es so, als ob der Zeichnungs-Namespace in dunklen Zeiten verlassen wurde. –

0

Vielleicht könnten Sie das Bild als BMP speichern, die mehr Speicherplatz nehmen würde, würde aber auf den Betrag senken zu verarbeiten, es würde tun müssen. Sie sollten verschiedene Bildformate ausprobieren, um zu sehen, ob die Komprimierung Probleme verursacht. Wenn dies der Fall ist, möchten Sie möglicherweise einige andere Threads ausgliedern, um danach die Komprimierung durchzuführen.

+0

Dies beantwortet nicht, warum es nicht parallel zu sein scheint. – Dykam

+0

Es kann parallel gehen, aber wenn Computer ist zu viel zu viel, dann könnten die Dinge tatsächlich verlangsamen. Vielleicht läuft der PNG-Encoder bereits parallel unter der Haube, und wenn Sie versuchen, parallele Instanzen davon auszuführen, werden zu viele Threads erzeugt, was dazu führt, dass Ihr Computer zu viel Kontext wechselt. Wie sieht die Prozessorauslastung bei der Ausführung mit 1 Thread aus, verglichen mit vielen Threads? Liegt es schon bei 100% mit 1 Thread? – Kibbee

+0

Ich habe versucht BMP (zusammen mit allen anderen Formaten), aber es hat nicht wirklich die parallele Leistung beeinflusst. – dewald

0

Denken Sie daran, Sie haben Hyper-Threading und wirklich Kerne. Sie müssen vorsichtig sein, Hyper-Threading hat nicht die gleiche Leistung wie ein Kern.

andere Sache, ein gutes Geschäft mit paralleler Ausführung zu arbeiten, während Sie Ihren Pool-Thread erstellen Sie eine maximale Anzahl von Threads wie

MaxThreads = Kerne setzen müssen - 2;

Wenn ich Kerne sage, lesen Sie Kerne nicht Hyper-Threading Kerne.

1 - OS 1 - Hauptanwendung X - Kerne verarbeiten.

Wenn Sie zu viele Threads erstellen, werden Sie wegen der Konkurrenz im Prozessor Leistung verlieren.

Erstellen Sie Jpeg oder Png Bilder ist ein weiterer guter Punkt, auf diese Weise werden Sie weniger Zeit auf HD nehmen, während Sie das Bild speichern. Passen Sie auf die Qualität der JPEG und Png auch, denn wenn es 100% ist, kann es groß sein.

Anderer Punkt, der relevant ist. Sie werden auf HD konkurrieren, weil viele Threads Archive auf ihm erstellen werden. Was können Sie dagegen tun? Es ist wirklich ein Problem, schwerer zu lösen, weil wir keine parallelen hds haben. Sie können also einen Ort erstellen, an den die Bilderpuffer gesendet werden, wie auch andere Threads, die nichts verarbeiten, einfach da sein und den Puffer der Bilder empfangen und zum Beispiel in einer internen Liste speichern. Und in 5 em 5 Sekunden (oder einige Bedingungen, die Sie denken, dass es besser ist), beginnt es, die Bilder auf HD zu schreiben. Sie werden also einen Thread haben, der nur "ohne" Konkurrenz auf HD arbeitet, und andere Threads, nur um die Bilder zu verarbeiten.

bei.

+0

@SaCi - Es ist kein HD-Problem, da ich es durch Schreiben in Stream.Null teste, so dass es niemals die HD berührt. Ich habe versucht, die maximale Anzahl der Threads auf 4 zu reduzieren (da ich auf einem 8-Kern-Rechner bin), aber das schien nicht zu helfen. – dewald

0

Wenn ich raten müsste, würde ich sagen, es sieht so aus, als ob der Code innerhalb SaveImage durch eine CriticalSection oder eine Sperre geschützt ist, die nur einen Thread gleichzeitig in Teilen dieses Codes laufen lässt.

Sie könnten also recht haben, wenn Sie Hintergrundthreads nicht verwenden dürfen, aber ich denke, es ist wahrscheinlicher, dass Sie nicht mehr als einen Thread gleichzeitig in SaveImage ausführen lassen dürfen. Die Dokumentation zu dieser Funktion ist ziemlich spärlich, aber die Zeitvorgaben sind sehr suggestiv. 4 Diagramme nehmen etwa 4 mal so lange wie 1 Diagramm.

Wenn Sie nur 1 Diagramm mit einem Hintergrund-Thread speichern - geht es mit voller Geschwindigkeit?

Verwandte Themen