Jump to content

Performance tweak for LV Classes and "To More Specific" node


Recommended Posts

I attempted to reply over on the NI forums, but there's something broken there at the moment, so I'll reply here:

post-181-0-92682200-1320257213.png

We've taught the To More Specific node to be aware when its output is unwired and not tell the compiler that it modifies the input object. That means that the first "To More Specific" just does the type testing and never modifies the object even if the cast fails, so the original object remains available for the case structure. The second "To More Specific" actually does the cast.

That's a super neat thing to know. But it leads me to the quesiton: how can I do that in *my* code? :)

Link to comment

Actually, what I'd like is just to know what inputs and outputs are connected to a subVI - that would be super sweet. Maybe I should post it over on the idea exchange.

I've always thought it would be handy to have a way to differentiate between an unwired terminal and a default-value terminal, not that I often come across a use case for it.

Link to comment

So the answer is "no", because:

Users have three existing options to solve this problem:

1) A sentinel value (as described by mechelecengr above)

2) If all the values in your range are valid values, create a cluster of the u8 and a boolean and use the boolean as the sentinel value.

3) Create a poly VI that has a junk type (perhaps boolean) on the first implementation listed in the polyVI and and the desired type (u8 in your example) on the other implementation. If the u8 is wired, LV will call the version where it is assumed the input is wired. Otherwise it will call the other, which is written to assume the input is not wired, which may be way more efficient because the runtime system doesn't even have to test anything.

Your request for this "is wired" property is a feature that I explicitly asked for early in my time in R&D. I learned that it is a bad idea because pretty much any proposed solution that we could do in the LV compiler would be LESS efficient than what a user could code on the diagram. To begin with, the Invoke node itself causes the FP to be loaded into memory in order to have anything to reference. That slows the execution of a subVI considerably right there. More, because we wouldn't know whether a given terminal would necessarily be asked for its "is wired" status, and because there is no block diagram for VIs in the runtime engine, every caller VI would have to compile into itself information about whether or not optional terminals were wired.

We can do various things like say "this method can only be selected on an implicitly linked Invoke node", which lets us flag callers of subVIs that they need to compile in "wired or not" records into themselves for particular subVI terminals. But that creates problems for Call By Reference nodes since something that had the "this terminal will be tested for 'is wired'" would have a different conpane signature from an otherwise identical conpane that did not test for "is wired".

In the end, this request is not really supportable by subVIs. Built-in functions can do it because they generate all of their code into the caller VI. Even if we someday have inline subVIs, this would require special code that told LV which portions of a diagram to inline based on whether or not a given terminal was wired -- probably a new structure node or something like that.

So, short story:

Yes, this has been contemplated extensively by R&D over the years.

No, we're not likely to do anything to implement this feature or anything similar to it.

We have Inline subVIs now :)

Link to comment

One thing I didn't reply on the NI forums (also because of the wonkiness that crelf likely experienced) is it might be nice to have the second class have a different wire than the first, as to emphasize where the wire types mutate.

Link to comment

We have Inline subVIs now :)

Yes, and this feature was discussed... and rejected since inlining doesn't prevent a VI from being used with the Call By Reference node. Also, very large VIs should not be inlined, and there's a real probability of people turning on inlining on something big just to get a feature like this that could still be better achieved by making it part of the function's signature or making a new type that has sentinel capacity (i.e., a class that has a boolean for "is initialized" and then the rest of the data).
Link to comment
...rejected since inlining doesn't prevent a VI from being used with the Call By Reference node. Also, very large VIs should not be inlined, and there's a real probability of people turning on inlining on something big just to get a feature like this...

Very true - that could certainly mess a bunch of different things up.

...could still be better achieved by making it part of the function's signature or making a new type that has sentinel capacity (i.e., a class that has a boolean for "is initialized" and then the rest of the data).

Please talk about that a little more - your post refers to another post ("as described by mechelecengr above") that doesn't seem to exist any more.

Link to comment

Option 1, the sentinel value, is having an input that is a uInt32, where 0 means "never set" and all positive numbers are valid input. There are variations on this theme, like using -1 to mean "infinite timeout" on various primitives in LabVIEW.

Option 2, clustering the data with a boolean, can be done just as well with a LabVIEW class and provides a better enforcement mechanism: Suppose you create a private data cluster of an integer and a boolean where the boolean means "Initialized", which defaults to False. The "Write Numeric.vi" could assign the integer and turn on the boolean. The "Read Numeric.vi" could return an error if the boolean was never set. With a plain cluster, you have the possibility of someone not checking the boolean and going straight to unbundling the integer.

For those of you who believe in the Tooth Fairy and other mythical creatures, an Option 4 would be to write an XNode, which can script different behavior if a terminal is unwired. But like all unreleased features, a certain amount of faith is needed to believe in that solution.

Link to comment

...but what about the use case that spawned this thread: not doing a bunch of extra work if the output of that work isn't even wired? Any ideas on how to do that at teh subVI level? I guess there could always be a ring where the programmer could just select what they do and don't want done, but if only the subVI was aware and could make that decision itself...

  • Like 1
Link to comment

...but what about the use case that spawned this thread: not doing a bunch of extra work if the output of that work isn't even wired? Any ideas on how to do that at teh subVI level? I guess there could always be a ring where the programmer could just select what they do and don't want done, but if only the subVI was aware and could make that decision itself...

If I'm understanding correctly, you just wrap your heavy-lifting code in a case structure that uses the sentinel value as its selector. I think that would be enough for LabVIEW to know what's up.

Link to comment

...but what about the use case that spawned this thread: not doing a bunch of extra work if the output of that work isn't even wired?

I can't speak for anyone else, but if I'm trying to change the output of a sub vi based on how (or if) downstream code is using it, that's a pretty good indicator something is wrong with my design. The easiest solution is to refactor the sub vi into smaller functional parts. Clearly the existing one is doing too much stuff in one step.

I posted this comment on the NI forum as well, but this thread is getting more activity:

So this might be a stupid question, but why are you overloading the To More Specific node with special behavior when the output is unwired? It seems like you're exposing a behavior TMS uses, but the behavior isn't really what TMS is for. Wouldn't it make more sense to create a new node that returns the boolean result from a type check? Personally I think an explicit type check would make the code much easier to read. And, since the node is right there on the palette, I wouldn't have to worry about trying to remember special behaviors or wade through years of old posts looking for that one nugget of information I vaguely remember reading about.
  • Like 1
Link to comment
I can't speak for anyone else, but if I'm trying to change the output of a sub vi based on how (or if) downstream code is using it, that's a pretty good indicator something is wrong with my design.

I don't agree - for exatly the reason that the original class primative was written this way: it has 2 functions: cast the class, and error out if you can't - using it without caring what the new class is out but only being interested *if* is can be cast is a perfectly valid use case in my opinion. I'm not saying that this is a technique I can think of using often, but it'd be nice to have it in my toolbox.

If I'm understanding correctly, you just wrap your heavy-lifting code in a case structure that uses the sentinel value as its selector. I think that would be enough for LabVIEW to know what's up.

Which is the same as I said with a ring selector - which is fine, as long as it's a required input and you you remember to change it.

Link to comment

only being interested *if* is can be cast is a perfectly valid use case in my opinion

I agree 100%. There are times when type checking is very useful. I'm not saying type checking is useless or even that the compiler optimization is a bad idea. Obviously it is an improvement. I just think language is much more readable if the question of type compatibility is answered by an "Is Type Of" or "Is Child Of" node rather than requiring us to resort to confusing semantics with the To More Specific node. Roughly speaking, the text equivalent of the sample Stephen posted is:


obj response;

response = ToMoreSpecific(Parent,Child);

if isNotError(response) {

  response = ToMoreSpecific(Parent,Child);

  response.setValue(54);

};

[/CODE]

Then there's also this little trick Stephen posted a while back for run-time type checking:

post-7603-0-47837700-1320449580.png

I appreciate the effort Stephen goes to to keep us informed and I hope my comments aren't perceived as an attack on him. The fact that he has to explain how to accomplish certain tasks efficiently should be a clue the language is not providing something developers need.

  • Like 1
Link to comment
I appreciate the effort Stephen goes to to keep us informed and I hope my comments aren't perceived as an attack on him. The fact that he has to explain how to accomplish certain tasks efficiently should be a clue the language is not providing something developers need.

No offense taken, Daklu. The technical feedback is generally valuable.

In this case, I suggest your statement is sometimes true, but not always, and I'm not sure whether it is true in this case, so I'm going to walk through my thought process publicly.

Let's start with this example. The code on either side of the black bar does the same functionality:

post-5877-0-20033200-1320513843.png

Suppose many users are writing the code on the left, which is less efficient than the code on the right. Is that a deficiency of the language? No. Is it a deficiency of our documentation? I'm not sure... it certainly doesn't seem like it is LabVIEW's job to be documenting all the possible trig identities. Maybe VI Analyzer could detect simple trig substitutions like this one, but it would be hard to teach it all the known interesting identities. So if I saw many customers writing the code on the left and posted "hey, you should use the code on the right", that wouldn't reflect a deficiency in the language.

I believe that the above demonstrates that that the assertion of "efficiency advisory implies language deficiency" is not universally true.

Next thought experiment: Advising users to use the Inplace Element Structure instead of Index Array and Replace Array Subset.

post-5877-0-47057100-1320514619.png

The structure node gives LabVIEW information about your intentions that is hard or impossible (depending upon the exact code you write) to derive otherwise, and the compiler can use that information to make more efficient code. This is an example of, essentially, using exactly the same two nodes, but using them in a specific way. Is the language deficient? No, it provides a way to make your intentions clear to the compiler. Is the compiler deficient? Yes. In much the same way as the trig identity substitution, the compiler is not smart enough to recognize what you're doing. So it is a lack of artificial intelligence. But it does demonstrate that an efficiency advisory to use a node (or pair of nodes) in a particular way is not necessarily a language deficiency.

That brings us to the current case of the To More Specific node. Does it add anything to the language to have a different node that does the type testing? Functionally it would be absolutely the same as the existing To More Specific node, but missing the "object out" terminal. Let's call it the "Is More Specific" node... and I'll refer to them as "Is" and "To" for short. If we have "To", there's no need, functionally, for "Is", as long as people know how to use it. In other words, adding the "Is" node would still leave us with the same problem of teaching people when to use "Is" vs. when to use "To". On the flip side, if we had "Is" in the palettes, it might make people ask the question, "Why do I need this other node when I already have this first node?" That's the effect today with the Inplace Element Structure -- just have the IPE struct makes people question, "When would I ever use this if I have the direct nodes?" So although the "Is" would be noise from a "does it do anything new" perspective, that extra static might make people question, thus aiding with the education problem. On the other hand, if people could always use the IPE, I suspect we would remove the individual nodes from the palettes and send everyone to the IPE always. But there are times when you only want one half of the pair, so the individual nodes stay. In this case, we have a situation where the "To" node would *always* suffice for the "Is" node.

Overall, we are talking about avoiding one object copy. Yes, that might be critical in some domains, I grant, but this is hardly the most exotic tool in the "hide the dots" toolbox. Is it really worth the extra node? I don't know. I'm inclined to say "no", but I suspect some part of me is saying that just because I'd have to take on the work of actually creating that node, which would be a relatively boring task to undertake compared to other things I could be working on, so in this instance, my objectivity is compromised (providing new functionality is almost always more interesting than rehashing existing functionality... I'm sure most of you, as programmers, have experienced that... unless it is someone else's code originally that totally sucks and you get to have great fun ripping out the ugliness).

So, that's my arguments for/against the "Is" node.

Roughly speaking, the text equivalent of the sample Stephen posted is:


obj response;

response = ToMoreSpecific(Parent,Child);

if isNotError(response) {

  response = ToMoreSpecific(Parent,Child);

  response.setValue(54);

};

[/CODE]

I'd say a better equivalent is this:

[CODE] Error err = ToMoreSpecific(object, targetType, NULL); if (err == noError) { Child *child; ToMoreSpecific(object, targetType, &child); child.setValue(54); }[/CODE]

  • Like 1
Link to comment

I believe that the above demonstrates that that the assertion of "efficiency advisory implies language deficiency" is not universally true.

Yup, you've effectively shattered my original assertion. (Touche!)

That brings us to the current case of the To More Specific node. Does it add anything to the language to have a different node that does the type testing?... If we have "To", there's no need, functionally, for "Is", as long as people know how to use it... Is it really worth the extra node?

I agree, with the 2012 To node there is no functional need for an Is node. My issues isn't with functionality, it's with clarity. At its core writing code is about communicating with the compiler. Writing readable code is about communicating with future developers. The code on the left is what we'll have with 2012. The code on the right is what it would look like with an Is node. Functionally they are identical, but the average developer is going to look at the code on the left and see needless duplication. In plain english it reads like this:

1. Downcast the object.

2. If the downcast doesn't return an error, downcast the object again and call the method.

Unless he knows the TMS node creates a copy when the cast fails, *and* knows that copy isn't created when the output terminal isn't wired, it looks like code that would show up on a "Look at what the dumb previous developer did" thread.

post-7603-0-69637200-1320603662_thumb.pn

By giving the type check its own node it allows us as developers to more effectively communicate with each other via code. The code on the right reads like this:

1. Check to see if the object can be downcast.

2. If it can, downcast it and call the method.

The intent is much more clear in the code on the right because the semantics flow naturally from the nodes we're using rather than requiring us to remember obscure details about the To node's inner workings. The future developer may wonder why I bothered with the typecheck instead of using a single downcast and casing out an error, but it doesn't look like a silly mistake and--as you mentioned--more naturally leads into "what's the difference" questions precisely because they are different. So to your question, "Is it really worth an extra node?", my response is yes, absolutely.

As long as we're talking about type checking, and... umm... you'll be working on it anyway (*fingers crossed*)... I'll throw this out there too. I have to write the code on the left occasionally and every time another developer runs across it I have to explain what it's doing. It sure would be nice to be able to write the code on the right. (And I think an "Is More Specific" node and an "Is" node compliment each other nicely.)

post-7603-0-41328200-1320603670_thumb.pn

I'd say a better equivalent is this:

You'd know better than I so I certainly won't dispute it. However, the differences in pseudocode clearly reflect which side of NI's firewall each of us is on. My interpretation is from the perspective of a Labview developer who uses the To node as a black box. Yours is from the perspective of a developer who knows how the node works. Take a look at your two ToMoreSpecific function calls. They have different arguments, even though the block diagram nodes they represent have the exact same wires connected to the exact same input terminals. To me that is very unintuitive and hard to remember behavior.

this is hardly the most exotic tool in the "hide the dots" toolbox.

I rarely pull out the Show Buffer Allocations tool. Generally speaking I don't find it very helpful in figuring out why memory is being allocated or where I should apply my efforts to get the most bang for my buck. I'm sure a lot of it has to do with me not understanding the tool well enough. Maybe that's part of why I'd rather have an "Is" node where it's obvious the only allocation is a boolean than rely on a "To" node that sometimes allocates and sometimes doesn't.

I'd have to take on the work of actually creating that node, which would be a relatively boring task

You're a team lead/manager aren't you? Do the pointy-haired thing and delegate. Call it a learning opportunity.

(Just out of curiosity, is it a lot of overhead to create a node? The type checking code is already there and just needs to be extracted from the To node. Easy cheezy, right? *Whistles a tune innocently while blatently ignoring my sig.*)

providing new functionality is almost always more interesting than rehashing existing functionality... unless it is someone else's code originally that totally sucks and you get to have great fun ripping out the ugliness.

Methinks you and I have very different ideas of "fun."

Link to comment

Unless he knows the TMS node creates a copy when the cast fails, *and* knows that copy isn't created when the output terminal isn't wired, it looks like code that would show up on a "Look at what the dumb previous developer did" thread.

Agreed. I understand the code, but clarity of intent is far from good. This trick is neat, but just like the use of the PRT primitive to extract a default value (which you've also shown and I've grown to use), I will file this trick as one which will always require a decent dose of commenting to explain my intention lest I review the code a year later and not remember what I was thinking, let alone another programmer.

Proliferation of the palette be damned, the single most important part of code is communicating intent. There's a reason it wasn't Perl that made the web what it is today, and a lack of power definitely wasn't it...

I rarely pull out the Show Buffer Allocations tool. Generally speaking I don't find it very helpful in figuring out why memory is being allocated or where I should apply my efforts to get the most bang for my buck.

Also agreed. Though it helps, it's far too vague and I find it never really answers any questions. I'd much rather have a better rundown over where my copies are being created. After two years of code tweaking, one of my applications still winds up using about 1.5x the memory it should when I tally up all the data it's keeping track of. The fact that I've been using LabVIEW for over 15 years, consider myself a competent architect and I still can't seem to get control over my memory footprint says something for the state of memory management in the language in my opinion.

Link to comment

index.php?app=core&module=attach&section=attach&attach_rel_module=post&attach_id=5863

Regarding this code fragment, wouldn't a high-performance, flexible, and clearly-readable solution be to add the functionality to the case structure itself? By implimenting these Ideas: Wire Class To Case Selector and Allow vi server reference type as case selector.

It would look something like this (the "default" case would return the parent "Message" wire type):

post-18176-0-26773900-1320663425.png

-- James

  • Like 2
Link to comment

Regarding this code fragment, wouldn't a high-performance, flexible, and clearly-readable solution be to add the functionality to the case structure itself?

Baby steps, James... baby steps. :)

Even with that I'd still want a separate node that could do type testing. I don't use the case structure every time I'm testing a numerical or string equality and I'd probably get pretty annoyed if I had to use the case structure for type testing.

Link to comment

For those of you who believe in the Tooth Fairy and other mythical creatures, an Option 4 would be to write an XNode, which can script different behavior if a terminal is unwired. But like all unreleased features, a certain amount of faith is needed to believe in that solution.

Here be dragons and all manner of strange beasts....

http://code.google.com/p/lavacr/source/browse/#svn%2Ftrunk%2F8.6.x%2FLabVIEW%20API%2FXNodes%2FConPane%20Wrapper

It worked when I first wrote it, haven't played with it since though...

Link to comment

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...

Important Information

By using this site, you agree to our Terms of Use.