Maintaining State Information in LabVIEW Applications, Part 1
I was having a discussion (sometimes called “arguing” ) with another engineer at NI about how to maintain state in a LabVIEW application. We disagreed on the best way to maintain state in his application. Since at least two LabVIEW experts don’t agree on this topic, I think it will make a good topic for this blog. We also talked about how LabVIEW could make some things easier for what he was trying to do.
In this multi-part post, I want to start by explaining what state information is and why you might need it. Then I want to explain different ways you might want to implement it, including a comparison to how other languages support state information.
In computer science, there’s a concept of a purely “functional” subroutine, in which the subroutine returns values which are only a function of the inputs to that subroutine. Such a function has no side effects on the state of the rest of the system.
Consider the “add” function, for example…
Given the same input values for x and y, the add will always produce the same result.
A subroutine that has one or more side effects can’t be “functional”. Let’s consider the case where we want to keep a running average of acquired data points. (You might do this if you want to smooth the data to remove noise.)
(Ignore my lack of error reporting in this diagram.) Here, we’re acquiring a single sample of data, software-timed at 250 Hz. You can see the data is a bit noisy, so we use the “Mean PtByPt.vi” to average the last 100 samples to filter the data. This “Mean PtByPt.vi” works by remembering a history of the last 100 values that it received. This is “state” that the VI is remembering in an uninitialized shift register.
Now suppose we wanted to handle two channels. What would we do? Here’s one approach…
We use two instances of “Mean PtByPt.vi”—one for each channel.
There’s an important concept that makes this work—the “Mean PtByPt.vi” is marked as reentrant, where clones are preallocated. If you’re not familiar with the reentrancy of VIs, please read the LabVIEW help for how to change reentrancy.
By default, new VIs in LabVIEW are not reentrant. If the “Mean PtByPt.vi” weren’t reentrant, what do you suppose would happen? It makes a mess of the data; that’s what happens…
Here, the data from the two channels intermingles, so the averaging VI essentially averages the two waveforms together. The red and blue waveforms nearly overlap and aren’t an accurate representation of either waveform.
I sometimes refer to this as the “N-up problem”. I’ve gone from a single channel (1-up) to two channels (2-up). If I’m not aware of how state is used in my subVIs, I will not realize that my application may not work correctly when I add the second channel. When that happens, my first temptation is to make a copy of the VI—“mean1.vi” and “mean2.vi”, so that they’re separate in memory. Please don’t ever do this. You have better options (most of which I’m covering in this blog post), such as reentrancy.
Okay, here’s a little extra credit homework before part 2 of this blog post. Another option for reentrancy in LabVIEW is “shared clones”. Would it be correct to make “Mean PtByPt.vi” reentrant using shared clones? Why, or why not?
Hint: Here’s what the data looks like using shared clones…
Find out in part 2.
I’ll give the extra credit a try at the risk of sounding stupid.
My understanding with shared clones is that a clone is created as it is needed. Therefore, it is possible (very unlikely in this instance) that only 1 clone is made and therefore we are back to the non-reentrant case. The more likely scenario is that the two threads can “switch” which clone they are using and therefore you will have the wrong averages. The graphs may look correct, but the means are likely contaminated with data from the other waveform.
PS It was good to see you at the CLA Summit.
I’d second that (including the Summit part btw)
Spontaneous parallelization of code whenever possible in LabVIEW is definitely a great feature… Your shared clone example is “saved” by LabVIEW wanting to execute both instances in parallel, so you really get allocation of two separate instances. I’d expect a forced sequence for these two would bring trouble in (I haven’t spent time trying though)…
Anyway, thanks for the great “Hint” picture. Is “Never believe what you see” the take-home message ?