Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
350 views
in Technique[技术] by (71.8m points)

c# - 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. 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?

Things I've tried are:

  • Creating a Rectangle in a Canvas for every arriving data point. This is too slow by a factor of 10.
  • The same approach, but drawing DrawingVisuals in a custom control. 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. Here I have to draw every point again on each call to 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?
  • Displaying each point as a pixel in a 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.

I the olden (i.e. non-WPF) days, you could e.g. put the neccesary data into the graphics memory, and have the graphics card deal with it at its convenience. Of cource, it would not actually be displayed at a rate faster than 60Hz, but you did not have to touch this data again.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

Using a WriteableBitmap will be the fastest approach. 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 i.e. occasional stalling whilst GC is carried out. You could experiment with low latency GC to mitigate this.

Update

Here is an example of using low latency GC:

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" i.e. rendering time.

Update 2

As I mentioned in my comment a while ago - are you batching updates to your 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. Try and update your bitmap on a more sensible frequency (e.g. 60 or 25 times per second), as the overhead of forcing a bitmap render will dominate performance at 10k-100k updates per second. Write to a buffer when you receive device updates, then periodically transfer this buffer to the WritableBitmap. You could use a timer for this, or do it every n device updates. In this way you will batch your updates and vastly reduce WritableBitmap render overhead.

Update 3

Ok, it sounds like you are updating the WritableBitmap 10k-100k times per second - this isn't feasible. Please try a frameatch based mechanism as described previously. Also your display is only likely to be updated at 60 frames per second.

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. As long as you can swap the buffer in < 10μs, you can do this in the dead time without blocking your device updates.

Update 4

Further to a response to my question, it would appear that there is currently a "lockunlock" being called for each of the 100k updates per second. This is what is likely killing performance. On my (high-powered) system I measured 100k "lockunlock" at ~275ms. 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 i.e. lock -> update -> unlock. 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. 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. This would reduce your locking overhead by a factor of 10.

Example benchmark code for locking overhead on 100k calls:

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

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...