在WPF中一次快速绘制大量矩形

我的应用程序从外部设备提供数据。 在每个数据点之后,有一个短的电子死区时间(大约10μs),其中没有其他数据点可以到达,我的应用程序应该使用它来处理和在散点图中在屏幕上显示数据。 我最重要的目标是不要超过这个电子死时间。 如何在基于WPF的应用程序中解决此问题,以及对不同方法进行基准测试的方法是什么?

我尝试过的事情是:

  • Canvas为每个到达的数据点创建一个Rectangle 。 这太慢了10倍。
  • 相同的方法,但在自定义控件中绘制DrawingVisuals 。 更好,但仍然有点太慢。 将可视/逻辑子项添加到树中可能会产生太多开销。
  • UserControl ,其中所有数据点都存储在数组中并显示在OnRender方法中。 在每次打电话给OnRender时,我必须再次提出每一点。 因此,该方法随着时间减慢,这是不希望的。 有没有办法告诉OnRender不要在每次传球时清除屏幕,以便我可以逐步绘制?
  • 将每个点显示为WriteableBitmap的像素。 这似乎工作,但我没有找到一种方法来确定,如果无效的部分Bitmap不会偶尔添加一些非常长的等待时间(当图像实际上在屏幕上刷新时)。 测量这个的任何想法?

编辑:

在评论中,提出了缓冲数据并以较慢的速率显示它的要点。 这种方法的问题是,在某些时候我必须处理缓冲区。 在测量期间执行此操作会导致我的系统繁忙且新事件将被丢弃的时间很长。 因此,单独处理每一点,但是为了好,将更加可取。 使用10μs触发每个事件的显示比将其快速存储到缓冲区要好得多,并且每50 ms左右使用100μs来处理累积的事件。

我是旧的(即非WPF)时代,你可以将例如必要的数据放入图形存储器中,并让图形卡在方便的时候处理它。 对于cource,它实际上不会以高于60Hz的速率显示,但您不必再次触摸此数据。

我希望我明确表达了我的要求。 我的英语=)

使用WriteableBitmap将是最快的方法。 对于测试,您可以预先分配数组并使用秒表在渲染时对时序进行采样,然后您可以分析时序以了解性能。

你有一个最重要的问题是垃圾收集。 遗憾的是,这会引入您所描述的确切性能问题的可能性,即在执行GC时偶尔会出现停滞。 您可以尝试使用低延迟GC来缓解这种情况。

更新

以下是使用低延迟GC的示例:

http://blogs.microsoft.co.il/blogs/sasha/archive/2008/08/10/low-latency-gc-in-net-3-5.aspx

您可以利用它来确保在“死时间”(即渲染时间)内没有垃圾收集。

更新2

正如我之前在评论中提到的那样 – 您是否正在对WritableBitmap进行批量更新?

您的设备更新频率太高,无法维持每次设备更新的位图写入 – 我认为每秒有10k-100k更新。 尝试以更合理的频率(例如每秒60或25次)更新您的位图,因为强制位图渲染的开销将主导每秒10k-100k更新的性能。 接收设备更新时写入缓冲区,然后定期将此缓冲区传输到WritableBitmap。 您可以使用计时器,或者每n次设备更新一次。 通过这种方式,您将批量更新并大大减少WritableBitmap渲染开销。

更新3

好吧,听起来你每秒更新WritableBitmap 10k-100k次 – 这是不可行的。 请尝试如前所述的框架\批处理机制。 您的显示器也可能以每秒60帧的速度更新。

如果您担心阻止设备更新,请考虑使用两个交替的后备缓冲区和multithreading。 通过这种方式,您可以定期切换设备写入的后缓冲区,并使用第二个线程将交换的缓冲区渲染到WritableBitmap。 只要您可以在<10μs内交换缓冲区,就可以在死区时间内完成此操作,而不会阻止您的设备更新。

更新4

在回答我的问题之后,似乎每秒100k更新中的每一个都会调用“lock \ unlock”。 这可能是杀戮性能。 在我的(高功率)系统上,我在~275ms处测量了100k“锁定\解锁”。 这很重,在低功率系统上会更糟糕。

这就是为什么我认为每秒100k更新无法实现,即锁定 – >更新 – >解锁。 锁定太贵了。

您需要找到一种方法,通过完全不锁定,锁定每n个操作,或者批量处理请求,然后在锁中应用批量更新来降低锁定调用次数。 这里有几个选择。

如果您进行批量更新,它可能只有10个周期,这会使您的更新频率降至每秒10k更新。 这会将锁定开销降低10倍。

用于锁定100k调用开销的示例基准代码:

 lock/unlock - Interval:1 - :289.47ms lock/unlock - Interval:1 - :287.43ms lock/unlock - Interval:1 - :288.74ms lock/unlock - Interval:1 - :286.48ms lock/unlock - Interval:1 - :286.36ms lock/unlock - Interval:10 - :29.12ms lock/unlock - Interval:10 - :29.01ms lock/unlock - Interval:10 - :28.80ms lock/unlock - Interval:10 - :29.35ms lock/unlock - Interval:10 - :29.00ms 

码:

 public void MeasureLockUnlockOverhead() { const int TestIterations = 5; Action> test = (name, action) => { for (int i = 0; i < TestIterations; i++) { Console.WriteLine("{0}:{1:F2}ms", name, action()); } }; Action lockUnlock = interval => { WriteableBitmap bitmap = new WriteableBitmap(100, 100, 96d, 96d, PixelFormats.Bgr32, null); int counter = 0; Action t1 = () => { if (++counter % interval == 0) { bitmap.Lock(); bitmap.Unlock(); } }; string title = string.Format("lock/unlock - Interval:{0} -", interval); test(title, () => TimeTest(t1)); }; lockUnlock(1); lockUnlock(10); } [SuppressMessage("Microsoft.Reliability", "CA2001:AvoidCallingProblematicMethods", MessageId = "System.GC.Collect")] private static double TimeTest(Action action) { const int Iterations = 100 * 1000; Action gc = () => { GC.Collect(); GC.WaitForFullGCComplete(); }; Action empty = () => { }; Stopwatch stopwatch1 = Stopwatch.StartNew(); for (int j = 0; j < Iterations; j++) { empty(); } double loopElapsed = stopwatch1.Elapsed.TotalMilliseconds; gc(); action(); //JIT action(); //Optimize Stopwatch stopwatch2 = Stopwatch.StartNew(); for (int j = 0; j < Iterations; j++) { action(); } gc(); double testElapsed = stopwatch2.Elapsed.TotalMilliseconds; return (testElapsed - loopElapsed); } 

完全披露:我已经为WriteableBitmapEx开源项目做出了贡献,但它不是我的图书馆,也不是我的所有者

为了通过chibacity添加出色的答案,我建议查看WriteableBitmapEx库。 这是一个出色的WPF,Silverlight和Windows Phone库,它向WriteableBitmap类添加了类似GDI的绘图扩展方法(blitting,lines,shapes,transforms以及批处理操作)。

最新版本的WBEx包含一个重构器,我执行该重构以允许批量操作。 WriteableBitmapEx库现在有一个名为GetBitmapContext()的扩展方法,用于返回一个包含单个锁定/解锁/无效块的IDisposable结构。 使用以下语法,您可以轻松批量绘制调用,并在结尾处仅执行一次锁定/解锁/无效

 // Constructor of BitmapContext locks the bmp and gets a pointer to bitmap using (var bitmapContext = writeableBitmap.GetBitmapContext()) { // Perform multiple drawing calls (pseudocode) writebleBitmap.DrawLine(...) writebleBitmap.DrawRectangle(...) // etc ... } // On dispose of bitmapcontext, it unlocks and invalidates the bmp 

WPF依赖于保留的合成引擎,这很酷,但看起来你更像是一个“简单”的原始位图显示。

我想你在这里有一个很好的例子: https : //web.archive.org/web/20140519134127/http : //khason.net/blog/how-to-high-performance-graphics-in -wpf /

如果我做对了,你有一个场景,你想要从你的传感器获取数据几秒钟 – 并显示它。 您有实时要求 – 或者您是否将特殊“相机”中的数据存储为图像,实时绘图仅用于演出?

如果是这样,你可以等几秒钟,然后显示结果?

听起来像WritableBitmap可以解决您的问题。 我假设每次你有一个锁定/解锁块都有一个开销,因为它与主题有关 – 所以我不认为每个点都是一个好主意。 为了获得它的计时,你可以在测试项目/测试数据上使用一个探查器 – 来自jetbrains的dotTrace是可以的 – 我认为他们有一个试用版。 您还可以使用性能计数器 – 这对其他东西也很有用。

我会让它multithreading并有一个高优先级的线程来处理输入点 – 或者你从你的设备中恢复中断? 据我所知,获得所有观点比直接绘制所有观点更为重要。

你写的WritableBitmap几乎不够快 – 所以对于你当前的解决方案,我会尝试保存对AddDirtyRect的调用,所以它只在每n个点/毫秒 – 转移到前缓冲区应该是快速的,即使它是一个大块。 你应该能够像使用wpf一样快速地获得它 – 它只是更好。

通过一些代码和您系统上的更多信息,它会更容易回答:)