我的应用程序从外部设备提供数据。在每个数据点之后,有一个短电子 死区时间(大约10μs),其中没有其他数据点可以到达,我的应用程序应该使用它来处理和显示 散点图中屏幕上的数据。我最重要的目标是不要超过这个电子死时间。 如何在基于WPF的应用程序中解决此问题,以及对不同方法进行基准测试的方法是什么?
我尝试过的事情是:
Rectangle
中创建Canvas
。这太慢了10倍。DrawingVisuals
。更好,但仍然有点太慢。将可视/逻辑子项添加到树中可能会产生太多开销。UserControl
,其中所有数据点都存储在一个数组中,并以OnRender
方法显示。在每次调用OnRender时,我必须再次绘制每一点。因此,该方法随着时间减慢,这是不希望的。有没有办法告诉OnRender
不要在每次通过时清除屏幕,以便我可以逐步绘制?WriteableBitmap
中的像素。这似乎有效,但我没有找到一种方法来确定,如果无效的部分Bitmap不会偶尔添加一些非常长的等待时间(当图像实际上在屏幕上刷新时)。有什么想法测量吗?修改
在评论中,提出了缓冲数据并以较慢的速度显示它的观点。 这种方法的问题是,在某些时候我必须处理缓冲区。 在测量期间执行此操作会导致我的系统繁忙且新事件将被丢弃的很长时间。 因此,单独处理每一点,但是为了好,将更加可取。使用10μs触发每个事件的显示比立即将其存储到缓冲区好得多,并且每50 ms左右使用100μs来处理累积的事件。
我是旧的(即非WPF)日,你可以例如将必要的数据放入图形内存,并让图形卡在方便时处理它。对于cource来说,它实际上不会以高于60Hz的速率显示,但你不必再次触摸这些数据。
我希望我明确了我的要求。我的英语=)
答案 0 :(得分: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。只要你可以在&lt;中交换缓冲区。 10μs,您可以在死区时间执行此操作,而不会阻止您的设备更新。
更新4
对于我的问题的回复,似乎每秒100k更新中的每一个都会调用“lock \ unlock”。这可能是杀戮性能。在我的(高功率)系统上,我在~275ms处测量了100k“锁定\解锁”。这个很重,在低功率系统上会更糟糕。
这就是我认为每秒100k更新无法实现的原因,即锁定 - &gt;更新 - &gt;开锁。锁定太贵了。
您需要找到一种方法,通过完全不锁定,锁定每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<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);
}
答案 1 :(得分:2)
WPF依赖于保留的合成引擎,这很酷,但看起来你更像是在“简单”和原始位图显示之后。
我认为你在这里有一个很好的例子:https://web.archive.org/web/20140519134127/http://khason.net/blog/how-to-high-performance-graphics-in-wpf/
答案 2 :(得分:2)
完全披露:我已经为WriteableBitmapEx开源项目做出了贡献,但它不是我的图书馆,也不是我与其所有者的关系
要通过chibacity添加优秀答案,我建议您查看WriteableBitmapEx库。这是一个出色的WPF,Silverlight和Windows Phone库,它将类似GDI的绘图扩展方法(blitting,lines,shapes,transforms以及批处理操作)添加到WriteableBitmap
类。
最新版本的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
答案 3 :(得分:1)
如果我做得对,你有一个场景,你想要从传感器获取数据几秒钟 - 并显示它。您有实时要求 - 或者您是否将特殊“相机”中的数据存储为图像,实时绘图仅用于演出?
如果是这样,你可以等几秒钟,然后显示结果?
听起来像WritableBitmap可以解决您的问题。 我假设每次你有一个锁定/解锁块都有一个开销,因为它与主题有关 - 所以我不认为每个点都是一个好主意。 为了获得它的计时,你可以在测试项目/测试数据上使用一个探查器 - 来自jetbrains的dotTrace是可以的 - 我认为他们有一个试用版。您也可以使用性能计数器 - 这对其他东西也很有用。
我会使它成为多线程并拥有一个高优先级线程来处理接收点 - 或者你是否从设备中恢复了中断?据我所知,得到所有观点比立即绘制所有观点更为重要。
你写的WritableBitmap几乎不够快 - 所以对于你当前的解决方案,我会尝试保存对AddDirtyRect的调用,因此它只在每n个点/毫秒 - 转移到前缓冲区应该是快速的,即使它是一个大块。 你应该能够像使用wpf一样快速地获得它 - 它只是更好。
通过系统中的一些代码和更多信息,可以更轻松地回答:)