Buffering Component Paint calls, and BOOST Asio


#1

Hi all,

I’m currently writing an app that continually reads input from a serial port using the Boost ASIO library. After reading the signal, which is just a PCM-encoded waveform, I plot it on screen. Ideally, the plot is done in a sort of slow trace oscilloscope fashion, so that successive samples are tacked onto the existing stream and the display is updated accordingly in a smooth marquee from left to right, and the whole thing wraps around every 10 seconds or so. The sample rate is 250 Hz, so that I read in one new sample every 1/250th of a second.

Right now, the unoptimized program is doing something like

Thread 1:

  1. (mutex on) Read new sample (mutex off)
  2. Add new sample to circular buffer of existing samples
  3. Have xxx amount of samples been read in since the last redraw? If so, pass pointer to buffer over to thread 2, which redraws the oscilloscope with th enew data
  4. Repeat

Thread 2:

  1. Give my oscilloscope class instance a pointer to the buffer
  2. (MessageManagerLock on) plot (MessageManagerLock off)
  3. End thread

That’s it, pretty simple.

If I try to redraw every sample, so that it’s theoretically as smooth as possible, this fails. Repainting the oscilloscope 250 times a second causes the whole thing to lag and crash. Additionally, since plots require the MessageManager thread to have lock, if I do 250 calls per second, the MessageManager thread basically locks the whole program up, and my initial thread doesn’t end up reading the serial port in a timely fashion, causing buffering issues. Or maybe it’s the creation of 250 new threads per second to plot which causes it to fail. Either way, that doesn’t work.

But, even if I only plot once every 5 samples or so, which is one plot per 20 ms, it still crashes. And if I plot once every 25 samples, which is one plot per 100 ms, it still crashes. I have to do it like once or twice a second for it to not crash.

What’s a better approach? Is there a way to do a realtime oscilloscope plot without everything hanging up? Perhaps there’s a way to paint first to a buffer without having to lock the MessageManager thread, and then just drawing the whole thing at once? Or maybe the creation of a brand new thread on every single redraw is what’s screwing it up…?

Thanks, and your help is much appreciated!


#2

[delete spurious double post]


#3

Yes, that’s utter madness!

Have one thread, which continuously collates some kind of minimal lump of data describing the current state that should be displayed (i.e. not an image, just the raw data). Then have a timer repainting the screen maybe at maybe 50fps max, and in your paint method, grab the latest lump of data from the thread, and draw it.

Make these data objects ref-counted, and you won’t even need to do any locking - the thread can be creating a new data object privately, then when it’s done, it can replace its current one with the new one, and when the UI thread grabs the latest one, it simply holds it for as long as it needs before releasing it.


#4

Hi Jules, and thanks for the timely response! And boy, do I mean timely…

[quote]Have one thread, which continuously collates some kind of minimal lump of data describing the current state that should be displayed (i.e. not an image, just the raw data). Then have a timer repainting the screen maybe at maybe 50fps max, and in your paint method, grab the latest lump of data from the thread, and draw it.

Make these data objects ref-counted, and you won’t even need to do any locking - the thread can be creating a new data object privately, then when it’s done, it can replace its current one with the new one, and when the UI thread grabs the latest one, it simply holds it for as long as it needs before releasing it.[/quote]

That sounds more sensible. To make sure I have the big picture right…

The minimal lump of data that describes the current state that should be displayed is the array of sample values. So FWIW, I have what I called “Thread 1” above, reading info from the serial port, extracting this info, and storing into the array. It does a huge lump read all at once, it doesn’t actually poll the serial port 250 times a second - I read as much as I can into a buffer, then read again once it’s full, etc. So I believe that meets the criteria for what you said about collating a minimal lump of data.

And then, if I understand you right, you’re saying to just have a single “Thread 2” which actively grabs data from this thread and plots it 50 times a second, rather than creating a ton of new threads. Right? So something like

Thread 1, the collater

  1. Read data from buffer, mutex and do a new serial port read if buffer runs out
  2. Parse data and put sample values in array that’s visible from Thread 2, defined below
  3. Sleep for 1/250th of a second and repeat

Thread 2, the plotter

  1. Observe this array of sample values*
  2. Plot it
  3. Sleep for 1/50th of a second and repeat

I put an asterisk above because I wasn’t sure how you were suggesting refcounting things to avoid locks… do I have the right picture of what you’re saying above?

Thanks again for the helpful and extremely quick response!


#5

Sorry, haven’t time to go into detail any more, but no! Use ONE thread, and do your drawing on the normal UI thread.


#6

Hi Jules - I implemented your suggestion and it’s definitely an improvement on the backwards way I was doing it.

I had a question about the threading dynamics though - I set it up using refcounted objects, but in this case my “minimal lump” of data is something like 90KB, because it’s the sample array with the actual data in it. If I literally update this every sample that means that I’m creating one new 90KB object and copying the old one into it 250 times a second. This is probably not ideal, right? So given that my minimal lump of data is quite large, would it be better to just have one lump and lock and unlock 250 times a second?

I should note that I’m doing this anyway, because (I think) I was running into problems where one thread would be reading the pointer at the same time the other would be changing it, or something like that, so I just temporarily put a mutex on for the operation where I update the pointer to the latest state descriptor object. So I might be defeating the whole purpose of this whole thing by thread locking when I change the pointer anyway…

If you have any insights, they’d be much appreciated! I’m still a bit shaky on the best way to handle some of the thread stuff in JUCE. And thanks again for all the help so far.


#7

Just to put my $0.02 in here…

On modern processors, if there isn’t lock contention (i.e. two threads getting the same lock over and over) then mutex-based locks are pretty cheap. It’s of course hard to benchmark or I’d suggest doing that, but it seems clear to me that taking and removing a lock is going to be at least a whole lot cheaper than allocating 90K, copying it and freeing it.

Just be careful and make sure that you don’t take any other locks while that one is held (or if you HAVE to, which does sometimes happen, that you always get the locks in the same order) - and that you don’t do anything that requires I/O or too much memory allocation while the lock is held!


#8

In general, I’d agree with you Tom, but in this case it sounds like his threads would be constantly fighting over the lock - the input thread will lock it hundreds of times a second, so the UI thread will probably have to wait for the lock most of the time, and will need to keep it locked while it does whatever complicated drawing operations are needed, which will almost certainly take so long that it would bother the real-time thread… I reckon it’d be much better to simply keep allocating new blocks of data, which will allow both threads to run without any interruptions at all. After all, the only extra overhead would be few hundred mallocs per second, which is peanuts, (and could even be avoided by re-using a pool of objects if it did turn out to be a problem)


#9

So if I do that, then how do I change the pointer without needing to lock it? When I change the ref counted pointer from my thread representing the current state, and I make it point to a different ref counted object because I’ve just updated it, does it change atomically?


#10

Yes, ref-counted pointers are atomic.