Jump to content

Testing for the same class


Recommended Posts

I have a use case where I should make sure two objects are of the same class. Not an instanceof test but one where I verify the final class of two objects are the same, like a typeof operation.*

So for example, even though a Class 1 inherits from LabVIEW Object, they are not the same because they are distinct classes despite the inheritance relationship.

For a generic method, I came up with this:

post-11742-0-91056800-1325741065_thumb.p

class test.zip

Am I missing something, there's no built in way to do this correct?

*I realize flat out that as soon as an OOP programmer needs to ask this, they're "doing it wrong". Work with me here though, sometimes timelines demand a short lived hack because redesigning the interface takes too long.

Link to comment

I'm not sure if your method fully works, because if the equal primitive only compares the data, it will presumably also return T if you have two different classes with the same data (which is obviously not something which is likely to happen).

In any case, I can think of another option where the code is simpler - Call PRTC twice, just like in your code, but instead of using a constant as the input, use 2 as the input for 1 and 1 as the input for 2 and then check the error. If neither has an error, that should mean they're of the same class. I didn't test this, but I believe it should work, because that should be the only case where you can successfully cast both to each other.

Link to comment
I'm not sure if your method fully works, because if the equal primitive only compares the data, it will presumably also return T if you have two different classes with the same data (which is obviously not something which is likely to happen).

The OP does works as each PRTC primitive will return the class' default data (which when compared will be equal if they are of the same class) because the code forces an error (at the PRTC).

<edit>

In any case, I can think of another option where the code is simpler - Call PRTC twice, just like in your code, but instead of using a constant as the input, use 2 as the input for 1 and 1 as the input for 2 and then check the error. If neither has an error, that should mean they're of the same class. I didn't test this, but I believe it should work, because that should be the only case where you can successfully cast both to each other.

That doesn't work as if the class' are of the same type no error will occur at the PRTC - so if the class are the same, but contain different data the equality will fail (when you want it to be true).

Link to comment

The OP does works as each PRTC primitive will return the class' default data (which when compared will be equal if they are of the same class)...

I understand how the code works. My theoretical case was of two different classes which have the same default data. Like I said, IF the equality primitive only checks the data (and I don't know if it does) then this case will return a false positive. There might also be other potential failures.

That doesn't work as if the class' are of the same type no error will occur at the PRTC - so if the class are the same, but contain different data the equality will fail (when you want it to be true).

I don't care about the equality. My suggestion was to only check the error (which is why I believe this should be more robust, as this is a casting mechanism, not a comparison mechanism). But again, I didn't test this, so we need some actual code.

Link to comment
I don't care about the equality. My suggestion was to only check the error (which is why I believe this should be more robust, as this is a casting mechanism, not a comparison mechanism). But again, I didn't test this, so we need some actual code.

Apologies - yes that works.

Link to comment

With regards to two distinct classes with the same data, I believe the OP ought to work as part of an object's value is what class its an instance of.

Regardless, I like Yair's suggestion better as there will be less on the diagram. I would need to test it to be sure LabVIEW wouldn't make extra copies of the inbound objects- if it does that could be expensive for objects with lots of data.

Either way, I will look into this soon.

Link to comment

I try to stay away from the path VIs as there's some overhead in them. Attached is a modified project containing the three methods deemed MJE, Yair, and Podsim as described above. All three methods are functionally equivalent and work as intended, proven by the three test VIs.

class test.zip

There's also a fourth test (Timing and Memory.vi) which involves creating an object with a fixed size payload, then running the comparison in a loop for a fixed amount of iterations.

0 MB size, 100 000 iterations:

MJE: 21 ms

Yair: 36 ms

Podsim: 3632 ms

The overhead in using the path methods is evident with the small payload. It's arguable if Yair's method is significantly slower at this point, but there might be some unwanted copies going on there causing the increase in time, or it might be noise.

10 MB size, 1000 iterations:

MJE: 0 ms

Yair: 4720 ms

Podsim: 2649 ms

Here the overhead in using the path VIs isn't as bad as what must be the overhead in generating large data copies. We see that indeed as I worried about with Yair's method: the likely cause of the slowdown is data copies (?).

Note the VIs are inlined, so memory profiling is not possible. If we remove inlining, the differences in timing vanish.

Link to comment

10 MB size, 1000 iterations:

MJE: 0 ms

Yair: 4720 ms

Podsim: 2649 ms

...the likely cause of the slowdown is data copies (?).

You should be able to neutralize that fairly easily, by using the same trick you used - cast a constant LVObject to both classes to get the default value of each class and use that default value for testing. This will complicate the diagram, but it's still more explicit (which means safer, at least in my book) than the comparison method.

Link to comment

There's a problem with your benchmarks: your benchmarks for MJE solution will vary depending upon the contents of the private data control of the class and which OS you're running on.

The solution posted by ShaunR is going to be the stable option regardless of the contents of the class. The performance of the MJE method will vary greatly depending upon the private data control of the objects being compared. Going through the Preserve Run-Time Class primitive will require a new allocation every time (because the wire starts with an instance of LabVIEW Object, copied from the constant, which is deallocated in favor of a new handle pointing to an object of the middle terminal's class). On a desktop system, that handle will just be assigned to point at the default value, since LV shares only one copy of the default value, but on real-time targets, that would be a full allocation (because everything has to be preallocated to avoid jitter in the event that the object gets passed into a deterministic section). Then we get to the equals primitive. In the case where the types do not match, you'll get a very quick answer because the type pointers can be compared. But if the types do match, if the class contains non-trivial data fields, you'll have the time overhead of actually comparing the fields. On a desktop system, again, we'll notice that they both point to the default value and you'll get a quick answer, but on real-time, we'll actually run the comparison proc to see if the values come out the same.

Now, if you're on a desktop system, the MJE, as I described above, does really well. I am guessing that this one would do even better:

post-5877-0-92677200-1325831621.png

Yes, that's a hardcoded zero iteration For Loop with the class wires wired across using tunnels, not shift registers. It does the job. I haven't benchmarked it -- you're free to do so -- but it avoids the type comparison work entirely that the Preserve Run-Time Class primitive does.

Having said all of that, even on RT, I would probably not use the stable solution because it requires the construction of those interim variants, which is a memory allocation. I just figured you should be aware of the trickiness of benchmarking this stuff.

On a completely unrelated topic, MJE, you can replace the error generation code you've got with this:

post-5877-0-62038900-1325832159.png

It's a lot simpler. The VI is in the palettes near the General Error Handler.vi.

Oh... and the path solution does have one problem: if a class has never been saved, the path will be empty.

Edited by Aristos Queue
  • Like 1
Link to comment

Now, if you're on a desktop system, the MJE, as I described above, does really well. I am guessing that this one would do even better:

post-5877-0-92677200-1325831621.png

Yes, that's a hardcoded zero iteration For Loop with the class wires wired across using tunnels, not shift registers. It does the job. I haven't benchmarked it -- you're free to do so -- but it avoids the type comparison work entirely that the Preserve Run-Time Class primitive does.

If you’ll forgive me… Why does that work?!? With no loop iterations the code can’t pass the objects across and must output default values. OK, but the default value of a LabVIEW Object wire is a LabVIEW Object; how does the child class identity get passed across the void when the actual child-class objects do not? That doesn’t seem right at all.

Another issue: aren’t all these object manipulating techniques rather obtuse code? Sort of LabVIEW alchemy? The uninitiated will be mystified as to why we are “preserving run-time class” or finding the path to a class in order to tell if A and B are the same type, let alone understand a zero-iteration loop.

— James

  • Like 1
Link to comment

> Another issue: aren’t all these object manipulating techniques rather obtuse code? Sort of LabVIEW alchemy?

I never said otherwise. You asked for a solution. You didn't ask for a *planned* solution. :-)

> Why does that work?!?

Because the defined behavior for LV classes is to be the default value of the object *as if the loop had iterated once*. It is a feature choice that makes automatic downcasting work a lot better.

Link to comment

Not a trick. Just seeing if anyone can read code based on this. Sometimes it is important to understand the esoterics of what the compiler is doing, in order to work with it in producing the most optimized code. But this is really esoteric, for something that seems like it should be trivial. From looking at the below image from NI.com I would naively say that determining if two objects are the same class should be trivially easy and blindingly fast on RT or desktop systems because we just compare the two type pointers. Why is there no primitive to do this?

post-18176-0-17232800-1325936846.gif

P.S. Anyone else care to guess what object comes out of the zero-iteration loop?

Edited by drjdpowell
Link to comment
Not a trick. Just seeing if anyone can read code based on this. Sometimes it is important to understand the esoterics of what the compiler is doing, in order to work with it in producing the most optimized code. But this is really esoteric, for something that seems like it should be trivial. From looking at the below image from NI.com I would naively say that determining if two objects are the same class should be trivially easy and blindingly fast on RT or desktop systems because we just compare the two type pointers. Why is there no primitive to do this? post-18176-0-17232800-1325936846.gif P.S. Anyone else care to guess what object comes out of the zero-iteration loop?

If I'm not mistaken here it seems that you think that the fundamental representation is byref rather than byval and I suspect that it is by val. This is, at least potentially, another reason for AQ's prior response...

>... Why does that work?!? Because the defined behavior for LV classes is to be the default value of the object *as if the loop had iterated once*. It is a feature choice that makes automatic downcasting work a lot better.
Link to comment

Objects are definitely passed by value, but the mechanism would have to be different than used for scalars. If I have a VI that expects a DBL, the compiler knows exactly the data space required for that DBL: 8 bytes. But the size of an object can't be determined until run-time because an Object wire must be able to also carry any class inheriting from the expected class. If you think about it, in order for LabVIEW to work efficiently with objects, it *must* use some method of indirection to avoid generating copies of objects for every call- not unlike what is done with arrays and other dynamically sized data.

I hadn't seen the document linked above previously, but the diagram showing the representation of classes makes sense. The indirection is handled through pointers.

I also agree that the lack of primitives to do fundamental operations on objects is annoying. Granted operations like this are rarely needed, and in general a sign of a fundamental problem with the design of a program. I acknowledge this, but it doesnt change the fact that I have to deliver something yesterday and adding this quick check can make my code robust right away (at the expense of extensibility), compared to spending a day re-engineering my class hierarchies and doing it the "right" way.

Link to comment
...Granted operations like this are rarely needed, and in general a sign of a fundamental problem with the design of a program. I acknowledge this, but it doesnt change the fact that I have to deliver something yesterday and adding this quick check can make my code robust right away (at the expense of extensibility), compared to spending a day re-engineering my class hierarchies and doing it the "right" way.

The same could be said about real globals, goto, getjmp() and setjmp(). Of course somewhere, "under the hood", pointers are used but the whole point IMHO is to specifically NOT exposed that, not make someone have to know the intricacies of optimizing for particular compilers, specific memory management, etc, etc because the G language handles that part of the "indoor plumbing" in a sane, predictable way -- if you actually let it do so.

And when it doesn't, then it's time for a CAR... :oops:

Link to comment
Couldn’t we have a few nice primitives for determining class relationships, so I don’t have to learn this weird compiler voodoo? :)

We put the classes together with priority on the operations we thought people were going to need regularly. Exact type equivalence is nearly anathema to good design, as MJE noted in his original post on this topic. Yes, we could create a prim to do exact type equivalence. But the question is, why are you creating any hierarchy where the child class cannot fully substitute for the parent class? That usually leads to serious problems and hacks piling onto hacks. So we spent time on other stuff. :-)

Edited by Aristos Queue
Link to comment

... but the whole point IMHO is to specifically NOT exposed that, not make someone have to know the intricacies of optimizing for particular compilers, specific memory management, etc, etc because the G language handles that part of the "indoor plumbing" in a sane, predictable way...

That’s my point. The “zero iteration loop” is producing results that depend on intricacies of the complier, which are not intuitive extensions of G. I agree with MJE in expecting my example to output the default object of the wire type, but we’re both wrong. Since as AQ said, the default behavior is to return the default object *as if the loop had iterated once* he would I guess expect the “Error Report Message” (the thing returned if the loop iterated once) but he’d be wrong also. The actual answer is “Temp Update Message”, an object would never be produced by the loop on any iteration.

Link to comment

We put the classes together with priority on the operations we thought people were going to need regularly. Exact type equivalence is nearly anathema to good design, as MJE noted in his original post on this topic. Yes, we could create a prim to do exact type equivalence. But the question is, why are you creating any hierarchy where the child class cannot fully substitute for the parent class? That usually leads to serious problems and hacks piling onto hacks. So we spent time on other stuff. :-)

This made me think of this past conversation of yours as an example of where having an extra type-checking primitive would be useful. Though, I see Daklu already made the same points in that conversation. You may have reason not to like a check for exact equivalence, but clearly people do have reason for some types of checks. And, remember this conversation? People have been using the object primitives that are available to substitute for operations that aren’t, and this leads to overly complex and unclear code.

Link to comment

That’s my point. The “zero iteration loop” is producing results that depend on intricacies of the complier, which are not intuitive extensions of G. I agree with MJE in expecting my example to output the default object of the wire type, but we’re both wrong. Since as AQ said, the default behavior is to return the default object *as if the loop had iterated once* he would I guess expect the “Error Report Message” (the thing returned if the loop iterated once) but he’d be wrong also. The actual answer is “Temp Update Message”, an object would never be produced by the loop on any iteration.

But we're still back to the same fundamental point: why do you really want to check for such "equivalence"? I know and understand that some people "clearly want to" as you point out but that does beg the question IMO. Isn't there at least one better way to architect so that checking for such equivalence isn't needed?

Link to comment

drjdpowell: I do remember those earlier conversations. I'd still like to see the actual code that you wrote so I could evaluate it in context. The text descriptions for architectures this large do not suffice -- I can see that an implementation that needs the exact type testing could work, but I can also imagine several other implementations that do not need type matching, and in my head, those work out as better solutions, but I can't assert that they necessarily would be better because you might have found a case where exact type testing really is necessary.

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
Unfortunately, your content contains terms that we do not allow. Please edit your content to remove the highlighted words below.
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.