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

Jen*_*ens 12 c# wpf

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

我尝试过的事情是:

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

编辑:

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

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

我希望我明确了我的要求.我的英语=)

Tim*_*oyd 11

使用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帧的速度更新.

如果您担心阻止设备更新,请考虑使用两个交替后备缓冲区和多线程.通过这种方式,您可以定期切换设备写入的后缓冲区,并使用第二个线程将交换的缓冲区渲染到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
Run Code Online (Sandbox Code Playgroud)

码:

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);
}
Run Code Online (Sandbox Code Playgroud)