I am using the .NET Chart Control library that comes with .NET 4.0 Beta 2 to create and save images to disk on a background thread. I am not showing the chart on the screen, however, simply creating a chart, saving it to disk, and destroying it. Something like this:
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);
}
}
It was taking about 300 – 400 ms to create and save each chart. I have potentially hundreds of charts to create, so I thought I would use Parallel.For() to parallelize these tasks. I have an 8 core machine, however, when I try to create 4 charts at a time, my chart create/save time increases to anywhere from 800 to 1400 ms, almost all of which is consumed by Chart.SaveImage.
I thought this might be a limitation of disk I/O, so to test that I changed the last line to:
ch.SaveImage(Stream.Null, ChartImageFormat.Png);
Even writing to a null stream the performance is still about the same (800 – 1400 ms).
Am I not supposed to create images on background threads in parallel with this library, or am I doing something wrong?
Thanks
EDIT: Added Complete Code Sample
Simply change the flag passed to CreateCharts() to test parallel versus serial.
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);
}
}
}
You’re running into problems with the
System.Drawingnamespace. There’s some heavy thread locking in there which will serialize certain tasks. Not until you callChart.SaveImage()does it actually render the image, that’s what’s eating all your time.If you change your test program up a bit you can see parallelization is happening, but it being severely hindered by the locking inside the Graphics drawing code.
Toy around with the
count = 50in the main method here… seeing both outputs at the same time helps I think, you can see that the Parallel one is consistently faster, though it doesn’t linearly scale because of the locking in the drawing namespace:What’s locking up is
Chart.SaveImage()->ChartImage.GetImage()->ChartPicture.Paint()