That depends on how you define rough. On most points, it agrees with
what has been presented at NIWeek and in tech notes. I'll refine a few
points below and mark them with ***s. Unfortunately, this is a bit long.
----------------
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.)
*** The list of nodes that are waiting asynchronously for something to
complete aren't really on a queue, and no LV execution takes place
there. There is a thread that deals with node timeouts and reschedules
them due to timeout. If the external event takes place, such as an
occurrence being set, then that thread can directly remove the node from
this list and schedule it where it is designated to execute.
According to app. note 114, the default execution environment provides...
*** The default changes from release to release, and generally is our
best guess at how to leverage the processors on the target computer.
This is configurable using the vi.lib/utility/sysinfo/threadcfg utility
which writes the configuration out to the ini file.
....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.
*** This implementation detail is a bit more complicated. If the call
must execute in a specific exec system, and different from the current
one, then yes, the node gets queued according to its priority. If it
can execute in the current thread, then I think that it will often start
immediately. If it doesn't start immediately, I think it is because the
subVI is part of another clump of code and cooperative multitasking is
taking place.
If a vi is non-reentrant, its tasks cannot run simultaneously on
multiple threads. This implies...
*** VIs not marked reentrant must have only call active in them at a
time, even if carried out by the same thread (in a single threaded
cooperative system). Multiple calls cannot be allowed to be in the
diagram at the same time and the calls queue up at the call according to
priority. Threads are not blocked from carrying out other execution.
I assume this to be a strictly labVIEW mutex and does not involve the OS.
*** LV uses some OS mutex constructions, and others are optimized for
quicker execution.
If a vi is reentrant, it can be posted/ran multiple times simultaneously.
*** Even in a single threaded system.
.... If a vi is a subroutine ... has no restrictions.
*** Subroutines are a lower overhead call, and are restricted to always
run in their callers execution system and cannot call anything that is
asynchronous or forces execution to a particular system or priority.
They also have no debugging info, and no panel interaction.
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.
*** As with most things, there is a tradeoff. Reentrant VIs each have a
unique dataspace preallocated so they are ready to run, whereas
non-reentrant VIs share a dataspace. The primary reason for making
something reentrant or not should be based upon the behavior you want on
parallel calls, this includes execution overhead, but isn't the only
consideration.
The execution queue to which a vi's tasks are posted depends upon the vi
execution settings and the caller's execution priority...
*** This part seemed a bit muddled. If the subVI isn't a subroutine,
then the subVI will be run in the execution system it specifies, but at
the higher of its priority and its caller's priority. UI execution
system is no different except that a single thread executes all UI
priorities, and priority only affects insertion into the queue. Via
configuration, it is possible to set other execution systems to run this
way too, but that isn't the default.
Additionally, certain nodes must execute in the "user interface"...
While the above generally specifies where a task will begin and end...
*** These nodes are no different than a subVI set to run in UI. All
execution context changes, not just ones to the UI, are done lazily to
try and amortize them and avoid excessive thread context switches. The
case to avoid is when you have nodes that require unique execution
systems sequentially wired to one another. In this case, LV will honor
all switches and the overhead cannot be optimized away. In reality, a
context change is measured in microseconds and if much work is being
done in the subVI, this isn't significant. By default, VIs are set to
run in the caller's system, again to avoid unnecessary switching.
Normally, tasks associated with a diagram run in one of the ?other?
execution systems...
*** The default is Standard. UI tasks is too generic a term. Accessing
the value of a control/indicator via the terminal and local is optimized
by having a buffer between them to limit the mutex friction. All
property accesses, whether implicit or explicit refnum are executed in
the UI thread, even the value property. Over time, some of this may
change, but again, you start trading space for speed/determinism, and
the current system seems to work quite well. By the way, even reads of
a property execute in UI -- they must prohibit modification by other
threads until the read completes.
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...
*** Reentrant VIs do not currently clone the panel, but they do have
their own version of control values. Double click on a particular subVI
call to switch the panel to that instance and view its values.
A note: It is said that the front panel data is not loaded unless the
front panel is opened...
*** A more complete note would say that a front panel isn't loaded if it
isn't open, it doesn't have modification needing to be saved, it
contains no property nodes, and no VI Server references to the panel or
objects on the panel are currently opened. Local and terminals only
touch the value, and they do not require the panel be loaded. And the
panel is loaded as a whole, if any control has a property node, the
entire panel must be loaded.
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?
*** A common misconception is that threads improve execution
performance. OS threads allow for multitasking and preemption. They
can improve determinism, but actually impose a performance penalty,
though if used well, the penalty can be quite small.
*** I've presented this info before, so let me conclude with the same
analogy I used there. The LV execution engine is like the internal
combustion engine under the hood of your car. You don't necessarily
need to understand how it works to make good use of it. Most people's
understanding of their car borders on magic. They push the gas pedal to
"give" gas to the engine, somehow it burns, ... and then the tires turn
to push them down the road. Probably everyone on this list knows better
than this, but few of us know all of what goes on, nor do we need to.
If we raise the hood and start changing things, the results are more
likely to be negative than positive.
Messing with priorities and execution settings on your VIs is akin to
raising the hood. The defaults were chosen because they are the best
overall settings, and until you know there is a problem, they are
probably better left alone.
Here is the strategy I normally use for achieving good performance from
LV. Write your code using subVIs that are good functional units --
around a page apiece. Design them to be reused and easy to debug and
document. Avoid using too many globals or locals forcing data here and
there -- use wires and dataflow instead. Pay attention to the algorithm
and keeping the data types simple and easy to read.
Once the application works, save everything, close subVI panels, and
take some timings. If it is slower than you would like, open the
profiler and measure why. Be sure to run the app to completion in order
to get timings of the top-level diagram. If a subVI executes many
nodes, typically via loops that execute many times, turn off debugging
for a boost. If a subVI is called many times, see if it can be made a
subroutine. By the way, many times is in the tens of thousands or up.
If your code is modular, the profiler will give good indication of your
performance glitches and rewriting a subVI, or even replacing it with a
DLL is much easier. You are also more likely to be able to run it in
isolation, tweaking and testing it without affecting the rest of your
application as much.
Greg McKaskle