Maintaining State Information in LabVIEW Applications, Part 5
In my last post, I showed one of the big downsides of using preallocated clones—when you have a large hierarchy of preallocated reentrant VIs, the number of clones in memory can get big fast.
In this post, I want to clarify one thing about reentrant hierarchies, and then start talking about how to work our way out of these programming problems.
From the last post, recall this hierarchy…
One of the things I wasn’t clear about is that I chose to make both B and C reentrant. But I didn’t have to. I could have, for example, made C non-reentrant…
What this means, though, is that even though we have three clones of B, they all have to share a single clone of C. This means that only one copy of C can run at a time, which might obviate the benefits of making B reentrant. (As always, it depends on your application. If C is called only occasionally, then it may not affect performance of the rest of B’s diagram.)
The main takeaway is that if you want the entire hierarchy to be reentrant, you have to set it on each VI in the hierarchy; it’s not automatically inherited from the caller. This gives you some flexibility on the time vs. space tradeoff.
Okay, so this series of blog posts is supposed to be about maintaining state, though we’ve made a big but important detour through reentrancy.
When we want a VI to maintain state, as well as be reentrant, what do we do? I never showed you the diagram for the running average VI…
I won’t get into the details of how it works; it’s not as easy as you might have expected. (It’s also not as well-commented as I would have expected. ) What I want to point out is the set of eight uninitialized shift registers on the left hand side of the loop. That’s where the state information is kept.
The state needed for this VI includes the running sum of data, the array of values that the VI remembers, the length of that array, the state of the internal state machine, error information, etc.
To let this VI use shared clones (and thus use less memory if this VI is called a lot), we need to move the state information out of this VI. How do we do that? Here’s one proposal: move the while loop up to the caller. It only runs once; it only exists to give the uninitialized shift registers a place to live.
In my first attempt to change this VI, I selected everything inside the loop and chose the “Edit –> Create SubVI” menu item. Definitely a first attempt that needs some refinement. Here’s the new subVI…
And here’s what the caller now looks like…
It’s kind of ugly, but it actually does solve the problem. The new subVI no longer has any state information in it. The state is all maintained at a higher level.
How can I improve this?
First, the new subVI doesn’t actually compute the average; that part was kept at the higher level VI. To be useful as a subVI, we should also move this code into the new subVI, so that it doesn’t have to exist in the caller.
Second, the caller’s code is clearly overwhelming with eight different inputs and outputs that it now has to keep track of somehow. These aren’t formally part of the API; they’re just needed to store state information for the VI to operate. My next attempt would be to put all of those in a cluster and make it a single cluster of state information (with eight elements inside the cluster) for the caller to maintain. In computer science terms, this is called an “opaque” data structure. The caller doesn’t have to know what’s inside; they just have to hang on to it for us from one call to the next.
Here’s a cleaner version…
And by the way, since I’m changing the connector pane, another change I would probably make would be to use an error cluster instead of an error code.
Here’s the before and after of the calling diagram. Here’s the original VI, where the state was maintained inside the subVI…
Here’s the after. This one uses shift registers to maintain the state cluster…
Other than the addition of the pink wires, nothing else changes much for the caller. The big benefit, of course, is that the “Running Average” subVI can now be made into a shared-clone reentrant VI, which can save memory.
Can this be further improved? You tell me. What are your suggestions?
From my understanding (and what you wrote in part 3), clones are always preallocated inside timed loops, so if you’re after using shared clones and reducing memory, moving the storage of state information is a right but not the final step in the right direction 😉
I’m wondering, why the display of the data is situated within the timed loop, I guess there’s no real use of changing the display this often.
So I would suggest sending the raw data to a parallel loop performing processing & display.
This way you could decouple the time critical aquisition and the (in this case) not so time critical processing / display. The drawback is the increase of memory usage due to data in the queues.
Oli
Good point, Oliver. If this were a time-critical part of my application, I should consider moving the user interface update out of the loop, and not updating it 250 times per second.
A few points…
Personally, I use timed loops even in non-time-critical situations. So I don’t want anyone to think that because I used a timed loop, that this is necessarily a time-critical part of my application.
For a simple application like this, I would be hesitant to introduce the additional complexity needed to either slow down the UI updates or move UI processing to another loop. If this were a more complex situation, I would probably change my mind.
But have I been right with my statement that in this case of the timed loop usage, the SubVI clones will be preallocated instead of shared?
I meant to reply to this much earlier, but yes. By default, reentrant VIs inside timed loops will be preallocated, even if they are set to shared. However, you can control this. Right click on the timed loop, and there’s a “Shared Clone Allocation” menu that lets you set this behavior.
I only learned about this a couple of weeks ago. Yes, there are some features in LabVIEW I don’t know about. 🙂
I’ll note the sentence “And by the way, since I’m changing the connector pane, another change I would probably make would be to use an error cluster instead of an error code”…
So, somebody at NI knows that error clusters are recommended over simple error codes. That’s good.
Hi !
I know this post is kind of old, but I’m facing similar problem ussing OOP. My child VIs are ‘dynamic dispatch’ VIs, so the VI as to be a shared reentrant VI. And my child VIs heavily use NI’s processing VIs, which are full of uninitialized shift registers… Where running 2 childs in parallel, then data is shared between the instances 🙁 !
Rewriting all the NIs VIs I’m using to extract the shift registers will be so painfull, inefficient and source of major problems.
Do you have any explanation on dynamic disptach forcing the VIs to be ‘shared reentrant ‘ ?
Hi, Cyril. Sorry your comment got lost in the comment moderation queue for a couple of weeks.
I would defer to Aristos Queue to answer on the NI or lavag.org forums for an official answer, but let’s think about it for a moment. If you were to preallocate the clones, you would have to preallocate them for every reentrant child VI that _could_ be called, since you don’t know until runtime which one will be called. Further, LabVIEW would have to dynamically allocate them for members of any dynamically loaded class. It quickly turns into a complicated mess, and would probably be a great source for new bugs. So, it seems more prudent to just disable this mess.
I’m curious which “processing VIs” have so many uninitialized shift registers. I’m sure there is plenty of poor code out there. As you discovered, those VIs really shouldn’t be written the way they are. Perhaps there is a newer version of those APIs that avoid the issue.