C# 在WPF中一次一个快速绘制大量矩形
我的应用程序从外部设备接收数据。在每个数据点之后,都有一个短的电子线路 死区时间(约10µs),在此时间内没有其他数据点可以到达,我的应用程序应该使用它来处理和显示数据 散点图中屏幕上的数据。我最重要的目标是不超过这个电子死区时间。 如何在基于WPF的应用程序中解决这个问题,以及如何对不同的方法进行基准测试 我尝试过的是:C# 在WPF中一次一个快速绘制大量矩形,c#,wpf,C#,Wpf,我的应用程序从外部设备接收数据。在每个数据点之后,都有一个短的电子线路 死区时间(约10µs),在此时间内没有其他数据点可以到达,我的应用程序应该使用它来处理和显示数据 散点图中屏幕上的数据。我最重要的目标是不超过这个电子死区时间。 如何在基于WPF的应用程序中解决这个问题,以及如何对不同的方法进行基准测试 我尝试过的是: 在画布中为每个到达的数据点创建一个矩形。这太慢了,慢了10倍 相同的方法,但在自定义控件中绘制DrawingVisuals。更好,但还是有点太慢了。将可视/逻辑子级添加到树
- 在
中为每个到达的数据点创建一个画布
。这太慢了,慢了10倍矩形
- 相同的方法,但在自定义控件中绘制
。更好,但还是有点太慢了。将可视/逻辑子级添加到树中可能会有太多开销DrawingVisuals
- 一种
,其中所有数据点存储在一个数组中,并显示在UserControl
方法中。在这里,我必须在每次调用OnRender时再次绘制每个点。因此,这种方法会随着时间的推移而变慢,这是不希望的。有没有办法告诉OnRenderOnRender
每次通过时不要清除屏幕,这样我就可以增量绘制OnRender
- 将每个点显示为
中的像素。这似乎是可行的,但我还没有找到一种方法来确定位图的无效部分是否会增加一些很长的等待时间(当图像在屏幕上实际刷新时)。有什么办法来衡量这一点吗WriteableBitmap
如果您担心阻塞设备更新,则考虑使用两个交替的反向缓冲区和多线程。通过这种方式,您可以定期切换设备写入的后台缓冲区,并使用第二个线程将交换的缓冲区渲染为可写位图。只要您可以在<10µs的时间内交换缓冲区,就可以在死区时间内进行交换,而不会阻止设备更新
更新4 进一步回答我的问题,似乎每秒100k次更新中的每一次都会调用“锁定\解锁”。这可能会破坏性能。在我的(高性能)系统上,我测量到约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 measureLockUnlockOverload()
{
常数int验证=5;
动作测试=(名称、动作)=>
{
for(int i=0;i
{
可写位图=
新西铁
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);
}
// 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