我正在使用.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);
}
}
}
答案 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个图表 - 是否全速运行?