简体   繁体   English

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

[英]Fast drawing lots of rectangles one at a time in WPF

My application is fed data from an external device.我的应用程序从外部设备获取数据。 After each data point, there is a short electronic dead time (of about 10µs) in which no other data point can arrive, which my application should use to process and display the data on screen in a scatter plot.在每个数据点之后,有一个很短的电子死区时间(大约 10 微秒),在此期间没有其他数据点可以到达,我的应用程序应该使用它来处理和以散点图的形式在屏幕上显示数据。 My most important goal is to not exceed this electronic dead time.我最重要的目标是不超过这个电子死区时间。 How would one approach this problem in a WPF based application, and what would be a way to benchmark different methods?在基于 WPF 的应用程序中如何解决这个问题,以及对不同方法进行基准测试的方法是什么?

Things I've tried are:我尝试过的事情是:

  • Creating a Rectangle in a Canvas for every arriving data point.Canvas为每个到达的数据点创建一个Rectangle This is too slow by a factor of 10.这太慢了 10 倍。
  • The same approach, but drawing DrawingVisuals in a custom control.相同的方法,但在自定义控件中绘制DrawingVisuals Better, but still a little too slow.更好,但仍然有点太慢了。 Adding visual/logical children to the tree may have too much overhead.将视觉/逻辑子级添加到树中可能会产生过多的开销。
  • A UserControl where all data points are stored in an array and displayed in the OnRender method.一个UserControl ,其中所有数据点都存储在一个数组中并显示在OnRender方法中。 Here I have to draw every point again on each call to OnRender.在这里,我必须在每次调用 OnRender 时再次绘制每个点。 This method therefore slows down over time, which is undesireable.因此,这种方法会随着时间的推移而变慢,这是不可取的。 Is there a way to tell OnRender not to clear the screen on each pass, so that I could paint incrementally?有没有办法告诉OnRender在每次通过时不要清除屏幕,以便我可以逐步绘制?
  • Displaying each point as a pixel in a WriteableBitmap .将每个点显示为WriteableBitmap一个像素。 This seems to work, but I 've not found a way to determine, if invalidating part of the Bitmap does not add a few very long wait times ocassionally (when the Image is actually refreshed on screen).这似乎有效,但我还没有找到一种方法来确定,如果位图的无效部分不会偶尔增加一些很长的等待时间(当图像实际上在屏幕上刷新时)。 Any Ideas for measuring this?测量这个的任何想法?

Edit:编辑:

In the comments, the point of buffering data and displaying it at a slower rate has been raised.在评论中,已经提出了缓冲数据并以较慢的速度显示数据的要点。 The problem with that approach is, that at some point I have to process the buffer.这种方法的问题是,在某些时候我必须处理缓冲区。 Doing that during the measurement introduces a long time during which my system is busy and new events would be discarded.在测量期间这样做会导致我的系统繁忙并且新事件将被丢弃的时间很长。 Therefore dealing with every point individually, but for good, would be more desireable.因此,单独处理每一点,但为了好的,会更可取。 Using 10 µs to trigger the display for every event is much better than storing it into a buffer in no time and use 100µs every 50 ms or so to process the accumulated events.使用 10 µs 触发每个事件的显示比立即将其存储到缓冲区中要好得多,并且每 50 毫秒左右使用 100 µs 来处理累积的事件。

I the olden (ie non-WPF) days, you could eg put the neccesary data into the graphics memory, and have the graphics card deal with it at its convenience.在过去(即非 WPF)时代,您可以例如将必要的数据放入图形内存中,并让图形卡在其方便时进行处理。 Of cource, it would not actually be displayed at a rate faster than 60Hz, but you did not have to touch this data again.当然,它实际上不会以高于 60Hz 的速率显示,但您不必再次触摸这些数据。

Using a WriteableBitmap will be the fastest approach.使用WriteableBitmap将是最快的方法。 For testing you could pre-allocate an array and use a Stopwatch to sample timings as you go about rendering, you can then analyse the timings to get some idea of performance.为了进行测试,您可以预先分配一个数组并在进行渲染时使用秒表对时序进行采样,然后您可以分析时序以了解性能。

One overriding issue you have is with garbage collection.您遇到的一个压倒一切的问题是垃圾收集。 This will unfortunately introduce potential for the exact kind of performance issues you describe ie occasional stalling whilst GC is carried out.不幸的是,这将为您描述的确切类型的性能问题带来潜在的可能性,即在执行 GC 时偶尔停顿。 You could experiment with low latency GC to mitigate this.您可以尝试使用低延迟 GC 来缓解这种情况。

Update更新

Here is an example of using low latency GC:以下是使用低延迟 GC 的示例:

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

You could leverage this to ensure that there are no garbage collections during your "dead time" ie rendering time.您可以利用它来确保在您的“死时间”(即渲染时间)期间没有垃圾收集。

Update 2更新 2

As I mentioned in my comment a while ago - are you batching updates to your WritableBitmap?正如我之前在评论中提到的 - 您是否正在对 WritableBitmap 进行批量更新?

Your device update frequency is too high to able to sustain writing to the bitmap for each device update - I think there are 10k-100k updates per second.您的设备更新频率太高,无法为每个设备更新持续写入位图 - 我认为每秒更新 10k-100k。 Try and update your bitmap on a more sensible frequency (eg 60 or 25 times per second), as the overhead of forcing a bitmap render will dominate performance at 10k-100k updates per second.尝试以更合理的频率(例如每秒 60 或 25 次)更新位图,因为强制位图渲染的开销将在每秒 10k-100k 更新时主导性能。 Write to a buffer when you receive device updates, then periodically transfer this buffer to the WritableBitmap.当您收到设备更新时写入缓冲区,然后定期将此缓冲区传输到 WritableBitmap。 You could use a timer for this, or do it every n device updates.您可以为此使用计时器,或者每 n 个设备更新一次。 In this way you will batch your updates and vastly reduce WritableBitmap render overhead.通过这种方式,您将批量更新并大大减少 WritableBitmap 渲染开销。

Update 3更新 3

Ok, it sounds like you are updating the WritableBitmap 10k-100k times per second - this isn't feasible.好的,听起来您每秒更新 WritableBitmap 10k-100k 次 - 这是不可行的。 Please try a frame\\batch based mechanism as described previously.请尝试如前所述的基于帧\\批处理的机制。 Also your display is only likely to be updated at 60 frames per second.此外,您的显示器可能仅以每秒 60 帧的速度更新。

If you are concerned about blocking your device updates, then consider using two alternating back buffers and multi-threading.如果您担心阻止设备更新,请考虑使用两个交替的后台缓冲区和多线程。 In this way you periodically switch which back buffer your device writes to, and use a second thread to render the swapped buffer to the WritableBitmap.通过这种方式,您可以定期切换设备写入的后台缓冲区,并使用第二个线程将交换的缓冲区呈现到 WritableBitmap。 As long as you can swap the buffer in < 10µs, you can do this in the dead time without blocking your device updates.只要您可以在 < 10µs 内交换缓冲区,您就可以在死区时间内完成此操作,而不会阻止您的设备更新。

Update 4更新 4

Further to a response to my question, it would appear that there is currently a "lock\\unlock" being called for each of the 100k updates per second.进一步回答我的问题,似乎目前每秒 100k 更新中的每一个都在调用“锁定\\解锁”。 This is what is likely killing performance.这可能会扼杀性能。 On my (high-powered) system I measured 100k "lock\\unlock" at ~275ms.在我的(高功率)系统上,我在大约 275 毫秒时测量了 100k“锁定\\解锁”。 That's pretty heavy and will be much worse on a lower powered system.这很重,在低功率系统上会更糟。

This is why I think 100k updates per second is not achievable ie lock -> update -> unlock.这就是为什么我认为每秒 100k 更新是无法实现的,即锁定 -> 更新 -> 解锁。 The locking is just too expensive.锁太贵了。

You need to find a way of bringing the number of locking calls down by either not locking at all, locking every n operations, or perhaps batching requests and then applying the batched update in a lock.您需要找到一种方法,通过根本不锁定、锁定每 n 个操作或批处理请求然后在锁定中应用批处理更新来减少锁定调用的数量。 There's a few options here.这里有几个选项。

If you go for a batched update, it could be as small as 10 cycles, which would bring your update frequency down to 10k updates per second.如果您进行批量更新,它可以小到 10 个周期,这将使您的更新频率降低到每秒 10k 次更新。 This would reduce your locking overhead by a factor of 10.这会将您的锁定开销减少 10 倍。

Example benchmark code for locking overhead on 100k calls:锁定 10 万次调用的开销的示例基准代码:

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

Code:代码:

public void MeasureLockUnlockOverhead()
{
    const int TestIterations = 5;

    Action<string, Func<double>> test = (name, action) =>
    {
        for (int i = 0; i < TestIterations; i++)
        {
            Console.WriteLine("{0}:{1:F2}ms", name, action());
        }
    };

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

WPF relies on a retained composition engine which is cool, but it looks like you're more after a "simple" and raw bitmap display. WPF 依赖于一个很酷的保留合成引擎,但看起来您更喜欢“简单”和原始位图显示。

I think you have a good example of what you want to do here: https://web.archive.org/web/20140519134127/http://khason.net/blog/how-to-high-performance-graphics-in-wpf/我认为你有一个很好的例子说明你想在这里做什么: https : //web.archive.org/web/20140519134127/http : //khason.net/blog/how-to-high-performance-graphics-in -wpf/

Full Disclosure: I have contributed to the WriteableBitmapEx open source project, however it is not my library nor am I affiliated with its owner完全披露:我为 WriteableBitmapEx 开源项目做出了贡献,但它不是我的图书馆,我也不隶属于它的所有者

To add to the excellent answer by chibacity , I would suggest looking at the WriteableBitmapEx library.要添加chibacity的出色答案,我建议查看WriteableBitmapEx库。 This is an excellent WPF, Silverlight and Windows Phone library which adds GDI-like drawing extension methods (blitting, lines, shapes, transforms as well as batch operations) to the WriteableBitmap class.这是一个出色的 WPF、Silverlight 和 Windows Phone 库,它向WriteableBitmap类添加了类似 GDI 的绘图扩展方法(位图、线条、形状、转换以及批处理操作)。

The latest version of WBEx contains a refactor that I carried out to allow batch operations.最新版本的 WBEx 包含我执行的重构以允许批量操作。 The WriteableBitmapEx library now has an extension method called GetBitmapContext() , to return an IDisposable struct which wraps a single lock/unlock/invalidate block. WriteableBitmapEx库现在有一个名为GetBitmapContext()的扩展方法,用于返回一个IDisposable结构,该结构包装单个锁定/解锁/无效块。 With the following syntax you can easily batch your drawing calls and perform only one Lock/Unlock/Invalidate at the end使用以下语法,您可以轻松地批处理绘图调用,并在最后只执行一次锁定/解锁/无效

// 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

If I get it right you have a scenario where you want to get data from your sensor for a few seconds - and show it.如果我做对了,你会遇到一个场景,你想从传感器中获取几秒钟的数据 - 并显示它。 You have a realtime requirement - or do you store the data from your special "camera" as an image and the realtime plotting is just for shows?您有实时需求 - 或者您是否将来自特殊“相机”的数据存储为图像而实时绘图仅用于节目?

If so you could wait the few seconds and then show the result?如果是这样,您可以等待几秒钟,然后显示结果?

It sounds like WritableBitmap could be a way to solve your problem.听起来 WritableBitmap 可能是解决您问题的一种方法。 I would assume that there is an overhead each time you have a lock/unlock block since it has to do with theading - so I don't think it is a good idea for each point.我会假设每次你有一个锁定/解锁块时都会有一个开销,因为它与标题有关 - 所以我认为这对每一点都不是一个好主意。 To get timing on it you could use a profiler on a test project / test data - dotTrace from jetbrains is ok - I think they have a trial version.为了获得时间,您可以在测试项目/测试数据上使用分析器 - jetbrains 的 dotTrace 可以 - 我认为他们有试用版。 You could also use a performance counter - that might be usefull for other stuff as well.您还可以使用性能计数器 - 这对其他东西也可能有用。

I would make it multithreaded and have a high priority thread to process the incomming points - or do you recive interrups from your device?我会让它多线程并有一个高优先级的线程来处理传入的点 - 或者你是否从你的设备接收中断? As i understand it is more important to get all point than to draw all points rightaway.据我所知,获得所有点比立即绘制所有点更重要。

You write that WritableBitmap is barely fast enough - so with your current solution I would try saving calls to AddDirtyRect so it is only at every n points/millisec - transfer to the frontbuffer should be fast even if it is a large block.你写的 WritableBitmap 几乎不够快 - 所以用你当前的解决方案,我会尝试保存对 AddDirtyRect 的调用,所以它只在每 n 个点/毫秒 - 传输到前端缓冲区应该很快,即使它是一个大块。 You should be able to get it just as fast with wpf as with forms - its just nicer.您应该能够使用 wpf 和使用表单一样快地获得它 - 它更好。

With some code and more info on your system it would be easier to answer :)在您的系统上使用一些代码和更多信息会更容易回答:)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM