LabVIEW

cancel
Showing results for 
Search instead for 
Did you mean: 

Is this roughly how the labVIEW Execution Systems work?

I've not taken a class in OS design, so I don't know the strategies used to implement multitasking, preemptive or cooperative. The description below is a rough guess.

LabVIEW compiles Vis to execute within its own multitasking execution environment. This execution environment is composed of 6 execution systems. Each execution system has 5 priority queues (say priorities 0->4). Vis are compiled to one or more tasks which are posted for execution in these queues.

An execution system may either have multiple threads assigned to execute tasks from each priority queue, or may have a single thread executing all tasks from all priority queues. The thread priorities associated with a multithreaded execution system are assigned according to the queue that they service. There are therefore 5 available thread priority levels, one for each of the 5 priority level queues.

In addition to the execution queues, there are additional queues that are associated with tasks suspended in various wait states. (I don't know whether there are also threads associated with these queues. It seems there is.)

According to app. note 114, the default execution environment provides 1 execution system with 1 thread having a priority level of 1, and 5 execution systems with 10 prioritized threads, 2 threads per priority queue. NI has titled the single threaded execution system "user interface" and also given names to the other 5. Here they will be called either "user interface" or "other".

The "user interface" system is responsible for all GUI actions. It monitors the keyboard and mouse, as well as drawing the controls. It is also used to execute non-thread-safe tasks; tasks whose shared objects are not thread mutex protected.

Vis are composed of a front panel and diagram. The front panel provides an interface between the vi diagram, the user, and the calling vi. The diagram provides programmatic data flow between various nodes and is compiled into one or more machine coded tasks. In addition to it own tasks, a diagram may also call other vis. A vi that calls another vi does not actually programmatically branch to that vi. Rather, in most cases the call to another vi posts the tasks associated with the subvi to the back of one of the labVIEW execution system�s queues.

If a vi is non-reentrant, its tasks cannot run simultaneously on multiple threads. This implies a mutex like construction around the vi call to insure only one execution system is executing the vi. It doesn�t really matter where or how this happens, but somehow labVIEW has to protect an asynchronous vi from simultaneous execution, somehow that has to be performed without blocking an execution queue, and somehow a mutex suspended vi has to be returned to the execution queue when the mutex is freed. I assume this to be a strictly labVIEW mutex and does not involve the OS. If a vi is reentrant, it can be posted/ran multiple times simultaneously. If a vi is a subroutine, its task (I think there is always only one) will be posted to the front of the caller's queue rather than at the back of the queue (It actually probably never gets posted but is simply mutex tested at the call.) A reentrant-subroutine vi may be directly linked to its caller since it has no restrictions. (Whether in fact labVIEW does this, I don�t know. In any event, it would seem in general vis that can be identified as reentrant should be specified as such to avoid the overhead of mutexing. This would include vis that wrap reentrant dll calls.)

The execution queue to which a vi's tasks are posted depends upon the vi execution settings and the caller's execution priority. If the caller's execution priority is less than or equal the callee's execution settings, then the callee's tasks are posted to the back of the callee's specified execution queue. If the caller's execution priority is greater than the callee's specifications, then the callee's tasks are posted to the back of the caller's queue. Under most conditions, the vi execution setting is set to "same as caller" in which case the callee�s tasks are always posted to the back of the caller's execution queue. This applies to cases where two vis are set to run either in the other execution systems or two vis are set to run in the user interface execution system. (It�s not clear what happens when one vi is in the �user interface� system and the other is not. If the rule is followed by thread priority, any background tasks in the �other� systems will be moved to the user interface system. Normal task in the �other� systems called by a vi in the �user interface� system will execute in their own systems and vice versa. And �user interface� vis will execute in the caller�s �other� system if the caller has a priority greater than normal.)

Additionally, certain nodes must execute in the "user interface" execution system because their operations are not thread-safe. While the above generally specifies where a task will begin and end execution, a non-thread safe node can move a task to the �user interface� system. The task will continue to execute there until some unspecified event moves it back to its original execution system. Note, other task associated to the vi will be unaffected and will continue to execute in the original system.

Normally, tasks associated with a diagram run in one of the �other� execution systems. The tasks associated with drawing the front panel and monitoring user input always execute in the user interface execution system. Changes made by a diagram to it own front panel are buffered (the diagram has its own copy of the data, the front panel has its own copy of the data, and there seems to be some kind of exchange buffer that is mutexed), and the front panel update is posted as a task to the user interface execution system. Front panel objects also have the advanced option of being updated sequentially; presumably this means the diagram task that modifies the front panel will be moved to the user interface execution system as well. What this does to the data exchanged configuration between the front panel and diagram is unclear as presumably both the front panel and diagram are executing in the same thread and the mutex and buffer would seem to be redundant. While the above is true for a control value it is not clear whether this is also true for the control properties. Since a referenced property node can only occur on the local diagram, it is not clear it forces the local diagram to execute in the user interface system or whether they too are buffered and mutexed with the front panel.

If I were to hazard a guess, I would say that only the control values are buffered and mutexed. The control properties belong exclusively to the front panel and any changes made to them require execution in the �user interface� system. If diagram merely reads them, it probably doesn�t suffer a context switch.

Other vis can also modify the data structure defining the control appearance and values remotely using un-reference property nodes. These nodes are required to run in the user interface system because the operation is not thread-safe and apparently the diagram-front-panel mutex is specifically between the user interface execution system and the local diagram thread. Relative to the local diagram, remote changes by other vis would appear to be user entries.

It is not clear how front panels work with reentrant vis. Apparently every instance gets its own copy of the front panel values. If all front panel data structures were unique to an instance, and if I could get a vi reference to an instance of a reentrant vi, I could open multiple front panels, each displaying its own unique data. It might be handy, sort of like opening multiple Word documents, but I don�t think that it�s available.

A note: It is said that the front panel data is not loaded unless the front panel is opened. Obviously the attributes required to draw an object are not required, nor the buffer that interfaces with the user. This rule doesn�t apply though that if property references are made to front panel objects, and/or local variables are used. In those cases at least part of the front panel data has to be present. Furthermore, since all data is available via a control reference, if used, the control�s entire data structure must be present.

I use the vi server but haven�t really explored it yet, nor vi reference nodes, but obviously they too make modifications to unique data structures and hence are not thread-safe. And in general, any node that accesses a shared object is required to run in the user interface thread to protect the data associated with the object. LabVIEW, does not generally create OS level thread mutexes to protect objects probably because it becomes to cumbersome... Only a guess...

Considering the extra overhead of dealing with preemptive threading, I�m wondering if my well-tuned single threaded application in LV4.1 won�t out perform my well-tuned multithreaded application in LV6.0, given a single processor environment�

Please modify those parts that require it.
Thanks�

Kind Regards,
Eric
0 Kudos
Message 1 of 18
(5,718 Views)
Hi Eric,

You are moving along nicely here.

I agrre that with your staement re: LV 4.1 vs LV 6.0.

I took the LV advanced course just before it was updated. Some of the examples that where performed in the class worked "backwards" because the proccessors were so fast that rather than gaining performance by running a task in a seperate thread, we actual saw a performance hit because of thread swaps!

You said that VI that are candidates for re-entrancy should generally be set that way (or something to that affect). THere is a seperate address space allocated for each instance of a re-enttrant VI. If the VI used used many times, you could be eating up memory needlessly. (I am showing my age by be concerned with memory req's).

More on re-entrant VI's.
I believ
e there can only be one front panel of a VI open at a time. So if you do a file>>>open of a re-entrant VI, LV can not tell which intsance of that VI you are interested in monitoring.
If you navigate through a diagram and double click on the icon of a re-entrant VI, LV now knows which instance you are interested in monitoring, and can show you the FP for that instance.

Ben

P.S. As soon as I read the topic of your question, I had a feeling this was you.
Retired Senior Automation Systems Architect with Data Science Automation LabVIEW Champion Knight of NI and Prepper LinkedIn Profile YouTube Channel
0 Kudos
Message 2 of 18
(5,716 Views)
This is a Five Star Question!

I already had two developers ask me about this posting.

Your questions have been an asset to this forum.

Ben
Retired Senior Automation Systems Architect with Data Science Automation LabVIEW Champion Knight of NI and Prepper LinkedIn Profile YouTube Channel
0 Kudos
Message 3 of 18
(5,716 Views)
Ben,

There are two types of memory which would be of concern. There is temporary and persistent. Generally, if a reentrant vi has persistent memory requirements, then it is being used specifically to retain those values at every instance. More generally, reentrant code requires no persistent memory. It is passed all the information it needs to perform its function, and nothing is retained. For this type of reentrant vi, memory concern to which you refer could become important if the vis are using several MBytes of temporary storage for intermediate results. In that case, as you could have several copies executing at once, your temporary storage requirements have multiplied by the number of simultaneous copies executing. Your max memory use is going to rise, and as labview allocates memory rather independently and freely, the memory use of making them reentrant might be a bit of a surprise.

On the other hand, the whole idea of preemtive threading is to give those tasks which require execution in a timely fashion the ability to do so regardless of what other tasks might be doing. We are, after all, suffering the computational overhead of multithreading to accomplish this. If memory requirements are going to defeat the original objective, then we really are traversing a circle.

Anyway, as Greg has advised, threads are supposed to be used judiciously. It isn't as though your going to have all 51 threads up at the same time. In general I think, overall coding stategy should be to minimize the number of threads while protecting those tasks that absolutely require timely execution.

In that sense, it would have been nice if NI had retained two single threaded systems, one for the GUI and one for the GUI interface diagrams. I've noticed that control drawing is somewhat slower under LV6.0 than LV4.1. I cannot, for example, make a spreadsheet scroll smoothly anymore, even using buffered graphics. This makes me wonder how many of my open front panel diagrams are actually running on the GUI thread.

And, I wonder if threads go to sleep when not in use, for example, on a wait, or wait multiple node. My high priority thread doesn't do a lot of work, but the work that it does is critical. I don't know what it's doing the rest of the time. From some of Greg's comments, my impression is it in some kind of idle mode: waking up and sleeping, waking up and sleeping,..., waking up, doing something and sleeping... etc. I suppose I should try to test this.

Anyway that's all an aside...

With regard to memory, your right, there are no free lunches... Thanks for reminding me. If I try this, I might be dismayed by the additional memory use, but I won't be shocked.

Kind Regards,
Eric
0 Kudos
Message 4 of 18
(5,716 Views)
Re-entrant VI's can have persistant memory.

I have a "point-by-point" function generator that I use to simulate user interface updates in a RT loop. This VI is re-entrant so that can track the type of signal being produced and the phase of the next output value. Because every instance of this VI may use a different update rate and a different signal type, I rely on each instance being able to remeber how it was last used.

I admit this is not a lot of memory but it is an example of multiple memory spaces for each instance.

Ben
Retired Senior Automation Systems Architect with Data Science Automation LabVIEW Champion Knight of NI and Prepper LinkedIn Profile YouTube Channel
0 Kudos
Message 5 of 18
(5,716 Views)
LV's default color depth changed between LV 5.1 and 6.0.

The new fancy graphics are stored in 16 bit format. Check the color depth on your machine. If it is not set to 16 bit, LV will work extra hard to do the drawing.

Ben
Retired Senior Automation Systems Architect with Data Science Automation LabVIEW Champion Knight of NI and Prepper LinkedIn Profile YouTube Channel
0 Kudos
Message 6 of 18
(5,716 Views)
Ben,

Absolutely... There are essential three types of shared code. There is shared code that uses the same persistent data for all calls. This would be non-reentrant code. There is shared code that uses persistent data for each instance, your case. And there is shared code that does not use persistent data, all data is passed to it.

All that I'm saying is that in a multithreaded environment, considering the mutexing requirements to protect non-reentrant code, code that is reentrant should be identified as such. If I have a vi that does nothing but add x+y, for example, it makes little sense for labVIEW to set up a mutex to protect it, nor for one thread to await another for access to it. Since it has no persistent data, the
additional memory requirement is only that required to store intermediate results. This memory is returned to the pool when the vi is done with it, or sometime later at labVIEW's discretion.

That's all meant.

Kind Regards,
Eric
0 Kudos
Message 7 of 18
(5,716 Views)
Ben,

The controls in question are actually the original LV controls so they are 8 bit, I believe. At any rate, to take advantage of font smoothing, I've to 16 bit color. I've tried the new controls and their performance does not differ from the old controls in 16 bit color. It must be something else.

Thanks though,
Kind Regards,
Eric
0 Kudos
Message 8 of 18
(5,394 Views)
> In that sense, it would have been nice if NI had retained two single
> threaded systems, one for the GUI and one for the GUI interface
> diagrams. I've noticed that control drawing is somewhat slower under
> LV6.0 than LV4.1. I cannot, for example, make a spreadsheet scroll
> smoothly anymore, even using buffered graphics. This makes me wonder
> how many of my open front panel diagrams are actually running on the
> GUI thread.

If you do this, you might as well allow all threads to access the panel
objects. And that would mean adding lots and lots of mutexes to protect
things. And since the panel objects are so highly connected, you
would often only have a single thread active at a time anyway. In
otherwords, in our opinion LV i
s better off like this, or single threaded.

> And, I wonder if threads go to sleep when not in use, for example, on
> a wait, or wait multiple node.

They sleep waiting for an item to be queued, but the real question is,
do they dream?

Greg McKaskle
0 Kudos
Message 9 of 18
(5,395 Views)
> All that I'm saying is that in a multithreaded environment,
> considering the mutexing requirements to protect non-reentrant code,
> code that is reentrant should be identified as such. If I have a vi
> that does nothing but add x+y, for example, it makes little sense for
> labVIEW to set up a mutex to protect it, nor for one thread to await
> another for access to it. Since it has no persistent data, the
> additional memory requirement is only that required to store
> intermediate results. This memory is returned to the pool when the vi
> is done with it, or sometime later at labVIEW's discretion.

I'm not sure what is meant by mutexing requirements. A mutex is
actually pretty small. In fact, every global variable larger than four
bytes has a mutex so that reads and writes are forced to take turns and
return consistent data.

Here are the situations:
There are the VIs that cannot have side-effects even though they have
state information, what the other postings referred to as persistent
data. This is the most common reason to make a VI reentrant -- to make
it work.

There are VIs that are pure functions, which will work whether reentrant
or not, but making them reentrant gets rid of contention. This doesn't
mean anything runs faster, but it allows for preemption and particularly
for higher priority items to cut in line.

Then there are VIs that work only if they are not made reentrant. One
example is a piece of HW or another outside resource whose state must be
kept consistent or protected. Another example is when calls have a
side-effect, such as a LV2 style global.

So IMO, the primary concern is correctness, and occasionally reentrancy
can help improve a high priority task.

On another topic, LV doesn't currently return data when a VI finishes.
There is an ini setting for doing this, but it really affects
performance, adds lots of non-determinism, and tends to fragment memory
to the point where the process uses quite a bit of extra memory. So the
current setting is to have subVI temporary buffers remain allocated
until the VI leaves memory or is recompiled.

Greg McKaskle
0 Kudos
Message 10 of 18
(5,717 Views)