mje Posted August 23, 2011 Report Share Posted August 23, 2011 I'm wondering what some of you have settled on as best practice for defining a class' interface when the main use of a class will be through a DVR to that class. Specifically I'm referring to the dynamic interface, that is the collection of member VIs which are dynamically dispatched. For those who haven't really explored class DVRs, the caveat is that all DVR refnums are strictly typed*. If I have a VI with an in-out set of terminals of type DVR<Type=Parent.lvclass>, it is a static interface. When I then create Child.lvclass, I can't override this VI with a new one having terminals DVR<Type=Child.lvclass>. If the user is only ever handed a DVR to one of these classes, we really only have two options as I see it. Option 1: Design the class entirely by value This leaves the onus on the user to have to wrap every call (or chain of calls) up in an IPE: The good news is this allows exact control over when you're locking down access to the refnum. For this reason alone I think this is the way to go, as the inherent synchronization this allows is a fundamental guard for data integrity if that DVR is floating around several different parallel tasks. The bad news is this way of doing things is ugly as sin, it can quickly become a mess of Case+IPE structures scattered throughout the diagram. Option 2: Create wrappers for the public API The idea is here we have a static VI which wraps access to each public dynamic VI: Usage of such a class looks something like this: OK, using the DVR is now much cleaner, but there's a few caveats: Lack of synchronization is in my opinion the biggest. There's an inherent lock/release involved with each call to a wrapper method. There's absolutely no guarantee that some other process won't operate on the DVR between the call to WrapperA and WrapperB. I'd have to add extra layer of synchronization to allow this, and to be honest, I already had one by virtue of the DVR. This is the fundamental reason I've abandoned this type of pattern. Though as long as I keep the by-value interface public, nothing would stop me from using a Case+IPE when I need to enforce an atom of operation... Another problem is extending the API with a child class becomes ambiguous. Maybe I want to add a new public dynamic method. What do I do? I can create a wrapper method, and have it operate on a DVR<Type=Parent.lvclass>, but this is an absolute nightmare as far as synchronization and type safety goes: You could also create the method typed to a Child DVR, but that really only changes where the cast is placed, and besides, if you're trying to insert the wrapper VI in a call chain with an API defined across multiple levels of inheritance, it becomes really messy and cumbersome. Learned this one through experience. Has anyone come up with a better "pattern" (for lack of a better word) when dealing with DVR based class interfaces? Experience has taught me that Option 2 is just plain broken. Option 1, although ugly and often cumbersome, is perfectly functional. Attached is a sample project (LV2010SP1). Regards, -michael DVR Interface Example.zip *I realize there's a whole other discussion about what could happen if DVRs weren't statically typed, or if we had another type of terminal which adapted to type, or if pigs could fly. But the reality here is right now we have static DVR refnums, so let's please restrict ourselves to how we work with them, not what could be. Quote Link to comment
Omar Mussa Posted August 23, 2011 Report Share Posted August 23, 2011 Option 3 (not shown) - Create a DVR reference and place that inside the lvclass private data. This means that you can't access the private data of the class without dereferencing the DVR. It also means that the public API never exposes the DVR. I use this option frequently and it seems to scale well in that I still have dynamic dispatching but my data is byRef due to the DVR. Quote Link to comment
mje Posted August 23, 2011 Author Report Share Posted August 23, 2011 I can't say I'm a fan of that method. Each level of the hierarchy then maintains its own DVR. Worse is you can't enforce that you're always dealing with a reference type. Once the class is extended, there's no contract keeping descendants from storing by-value data in their class data. Someone who's handed a wire several levels down the hierarchy has no way of knowing if they can branch the wire or not. 1 Quote Link to comment
Jon Kokott Posted August 23, 2011 Report Share Posted August 23, 2011 Basically what you want is to give your user the ability to define when consecutive methods should be acted on atomically. The best way I have of achieving this is by creating "command" class hierarchy. All of your "methods" are dynamically dispatched by the command class received. One of these commands is actually a recursive structure which contains an array of type "command." This is kind of a pain in the ass to program but is highly flexible. So to recap: you have a Class which is represented BY REFERENCE. all "methods" ARE BY VALUE. you 2 wrapper functions which are called "execute one command" and "execute multiple commands" the only difference between these two is that one takes an array of command to operate on the object, and the other only takes one. All calls will consist of populating the "command Class" with its associated data, then calling the generic execute one command VI. Keep in mind that this will require that the dynamic dispatch "command"::DO.vi to be reentrant. Option 3 (not shown) - Create a DVR reference and place that inside the lvclass private data. This means that you can't access the private data of the class without dereferencing the DVR. It also means that the public API never exposes the DVR. I use this option frequently and it seems to scale well in that I still have dynamic dispatching but my data is byRef due to the DVR. Man I just had a big discussion about whether the DVR goes inside the class or is of the class. I prefer the class to be A DVR of the class. Its important for the reason that mje describes above, and I think it unnecessarily obfuscates the operation of the class. By value or By reference is a fundamental enough concept that the user should know the "rules" of using the class. The API itself being by reference certainly tells you something about thread safety. Quote Link to comment
Jon Kokott Posted August 24, 2011 Report Share Posted August 24, 2011 This is basically the best thing I've come up with for dealing with references. This could easily be converted to a messaging system because it acts on a command class and not a directly exposed API. Of course person could have its own class hierarchy but for the purposes of this example I didn't go into that. Block Diagram All Commands Call "Do.vi" DO.vi unwraps the In place element structure, and calls the dynamic dispatch instance of "Action.VI" which is in the command hierarchy Methods of "person.lvclass" are called within the individual "Action" VIs MultiCommand can call multiple commands without leaving the in place element structure. here is its block diagram, it is a recursive call Here is the output of this group of calls, it shows the unwrapping/wrapping of the reference I think this is basically the most extensible way to do a by reference API ~Jon DVR Intervace 9p0.zip 2 Quote Link to comment
mje Posted August 24, 2011 Author Report Share Posted August 24, 2011 Interesting. You're right, add in some transport layer and you have a messaging system. In fact looking at your code I realize I've done exactly this for state machines in the past (the person being the model, and each method is a state). You just turned the problem up on it's head for me, I never thought to apply something like that to this problem. Cool indeed, -m 1 Quote Link to comment
Jon Kokott Posted August 25, 2011 Report Share Posted August 25, 2011 Yeah thats pretty much where I got this idea from. I actually started doing this with a queued command class operating on person with a while loop and the "actionl.vi" in like 8.6. If you look at the actor framework, thats pretty much the core operation. When DVRs came out I looked at the IPE as a way to "walk around" with the shift register, and use that same architecture. I find the DVR implementation easier to debug in most cases, the only issue is that the recursive call does make debugging a little harder. I usually wait till i need that feature, or don't do it at all. FYI i've benched using a DVR vrs a dynamically launched process running off a queue (messaging system) (in 2011) and the messaging system is 4x faster (which I wouldn't have guessed.) Here is the trade offs for using a DVR in this fashion vrs a messanging system: Messaging: Slower initialization (like 10 times slower, this only applies to running with a 2011 run async node) Faster command operation (about 4 times faster) this is a more important time to be fast harder to debug calls can be made directly in the event structure without worry of blocking the user interface since all we do is enqueue some data. 2 references to deal with (the queue reference, and the async node reference) DVR: Easier to debug (of course this is just my opinion) calls are "blocking" as in when the function finishes its really done. you might actually want this feature for an API since the user wants to know when the function completes for synchronization purposes and an additional reply layer will be required for a messaging system. much faster initialization of variables. This will be better for short lived objects. This type of API I've found is much easier to deliver to people who aren't totally into .lvclasses, and advanced architectures. Daemons scare people sometimes. In my opinion, the current "state of the art" for labview IS messaging systems. If you want to drive your coding by performance (maintainability is first, but I think I've shown these are basically equivalents as far as that goes) then messaging systems are better. DVRs are certainly not useless, and there are lots of good reasons to pick them. They just work a little differently. I find myself using both. I prefer to represent hardware as a DVR, but most other software operations are synchronized with other types of patterns. I feel like messaging is inherently event based, and DVRs are inherently polling mechanisms. The different types are better for different things. ~Jon I might take back that statement about being 4x faster for messaging. I'm not sure the bench I did accurately measured that particular operation. The timer didn't account for the multithreading involved. ~Jon Quote Link to comment
NATE Posted September 25, 2011 Report Share Posted September 25, 2011 Messaging architectures aside, I think it's still important to have an easy way to access objects by reference independent of frameworks and addon tools. This thread motivated me to present this as a topic at the next Advanced Phoenix Area LabVIEW User Group (PLUG+), Oct 4th. Pulling largely from this thread, along with prior experience and looking into G#, I put together this presentation. I don't consider myself an expert on the topic, but I do consider myself a student. I would very much appreciate feedback from LAVA users regarding the accuracy and completeness of this presentation. I'll incorporate your feedback and repost the presentation after Oct 4th. https://decibel.ni.com/content/docs/DOC-18320 Thanks in advance, Nate Moehring Quote Link to comment
mje Posted September 27, 2011 Author Report Share Posted September 27, 2011 There's an impressive amount of information in that presentation. How long are your meetings by chance? Quote Link to comment
ned Posted September 27, 2011 Report Share Posted September 27, 2011 From your presentation: In what ways are SEQs better for By Reference access than Notifiers? Perhaps the timing information associated with Notifiers is unwanted overhead. The way you've demonstrated the use of SEQs is not the way I typically think they're used in a By-Ref implementation, although I could be mistaken about how others use them. I would use a SEQ in a way that mirrors a DVR: never use Preview Queue and always dequeue before enqueue. This way the queue provides its own locking mechanism, no need for a separate lock. This is also more memory-efficient - you're never making a copy of the data in the queue, which can happen when you use preview queue. This use of a SEQ helps answer your question about queues versus notifiers - you could not achieve this behavior with notifiers (unless you wanted to use cancel notification instead of dequeue element - it might work, I haven't tried it). Quote Link to comment
NATE Posted September 28, 2011 Report Share Posted September 28, 2011 Thank you for your comments. Our meetings are 2 to 3 hours, we try to limit them two 1 hour presentations, which some time for announcements and dialogue. A lot of this presentation is about the By Reference Object Manager which I created prior to DVRs, so I'll be glazing through the slides. I realize I'll be pushing my hour but it's okay if I go a little long. Yes, I realize my BROM is using SEQs in a non-traditional sense, and that's one of the reasons why I still have it in the presentation even though it is superseded by DVRs. I state this in the presentation. I believe that DVRs should provide a way to retrieve a copy of the data without locking the data. This implementation of the SEQ, having a "Get Latest" method that uses the Preview Queue element gives me that capability. This is useful for WORM type applications where you populate the data at start up and reference the data through out an application with numerous parallel tasks. I don't want those parallel tasks to block on each other just to read some data out when I know I have no intention of updating the data. Obviously the normal "Checkout" method still exists, preventing memory copies. Also you may have noticed the locking semaphore. This essentially implements the other half of the IPE in a DVR implementation, not found in most SEQs. The caller who performed the checkout is given a passcode, the checkin operation will fail unless you provide that passcode back to the BROM. Pretty cool I thought. And yes, wrt to using Notifiers instead of Queues, I was thinking of the Cancel Notification primitive to prevent memory copies. I don't know if there's inefficiencies in doing this or not. Like I said, notifiers have timing features that Queues do not, which makes me think they would be less efficient than SEQs. I was just trying to be thorough and list possible ways of passing data by reference. Thanks again for your posts, Nate Quote Link to comment
NATE Posted September 28, 2011 Report Share Posted September 28, 2011 I'm sorry, you're right. In this presentation my Checkout method does leave the object in the SEQ. Again, this is because I don't want other read-only tasks to block waiting for a task to checkin the object back in. In other implementations I have used the traditional Dequeue Element SEQ technique. Quote Link to comment
mje Posted September 28, 2011 Author Report Share Posted September 28, 2011 I would use a SEQ in a way that mirrors a DVR: never use Preview Queue and always dequeue before enqueue. This way the queue provides its own locking mechanism, no need for a separate lock. Quite true, the whole point of the SEQ/DVR is to provide a locking mechanism such that you don't need a secondary semaphore or similar object. When you dequeue the SEQ element or enter an IPE with a DVR, it becomes impossible for any other task to get a value from it until you enqueue a new value or exit the IPE. However using the preview primitive is still normal in many situations when using an SEQ implementation. Consider having multiple views which must render the data in a common SEQ. The views likely have no interest in modifying the value, so they could just as easily use a preview to get their own local copy to do what they want with, there is no need for an explicit check out/in. I do this all of the time in my code, however I don't use SEQs anymore (moved exclusively to Object DVRs). What I often end up doing is signalling my views to update themselves with new data from some model, and the view will then pull off all the data it needs from the model via a single property node, generating a local copy of the data for it to use. Of course this isn't always the way it works, for example when a copy would be prohibitive from a memory stand point, but that brings up a whole other can of worms involving data models and what not. I would use a SEQ in a way that mirrors a DVR: never use Preview Queue and always dequeue before enqueue. This way the queue provides its own locking mechanism, no need for a separate lock. Quite true, the whole point of the SEQ/DVR is to provide a locking mechanism such that you don't need a secondary semaphore or similar object. When you dequeue the SEQ element or enter an IPE with a DVR, it becomes impossible for any other task to get a value from it until you enqueue a new value or exit the IPE. However using the preview primitive is still normal in many situations when using an SEQ implementation. Consider having multiple views which must render the data in a common SEQ. The views likely have no interest in modifying the value, so they could just as easily use a preview to get their own local copy to do what they want with, there is no need for an explicit check out/in. I do this all of the time in my code, however I I would use a SEQ in a way that mirrors a DVR: never use Preview Queue and always dequeue before enqueue. This way the queue provides its own locking mechanism, no need for a separate lock. Quite true, the whole point of the SEQ/DVR is to provide a locking mechanism such that you don't need a secondary semaphore or similar object. When you dequeue the SEQ element or enter an IPE with a DVR, it becomes impossible for any other task to get a value from it until you enqueue a new value or exit the IPE. However using the preview primitive is still normal in many situations when using an SEQ implementation. Consider having multiple views which must render the data in a common SEQ. The views likely have no interest in modifying the value, so they could just as easily use a preview to get their own local copy to do what they want with, there is no need for an explicit check out/in. I do this all of the time in my code, however I <p style="font-family: 'Helvetica Neue', Arial, Verdana, sans-serif; font-size: 14px; color: rgb(34, 34, 34); background-color: rgb(255, 255, 255); "> I would use a SEQ in a way that mirrors a DVR: never use Preview Queue and always dequeue before enqueue. This way the queue provides its own locking mechanism, no need for a separate lock. Quite true, the whole point of the SEQ/DVR is to provide a locking mechanism such that you don't need a secondary semaphore or similar object. When you dequeue the SEQ element or enter an IPE with a DVR, it becomes impossible for any other task to get a value from it until you enqueue a new value or exit the IPE. However using the preview primitive is still normal in many situations when using an SEQ implementation. Consider having multiple views which must render the data in a common SEQ. The views likel Quote Link to comment
Matt W Posted October 1, 2011 Report Share Posted October 1, 2011 I have a method for handling syncing with DVR classes, but it's a bit messy and I haven't used it behind checking that it seems to work. You basically wrap the class in a temporary dvr for the synced operations (It can implemented much cleaner with SEQ Classes). Just throwing it out there in case it gives someone an idea. Quote Link to comment
Aristos Queue Posted October 3, 2011 Report Share Posted October 3, 2011 mje: I agree with Option 1. If you're interested in details, I've explained my thinking on this elsewhere on LAVA and my position has not changed. Quote Link to comment
NATE Posted October 13, 2011 Report Share Posted October 13, 2011 (edited) I've posted a new and improved version of this presentation. I think this version is much better than the original, so even if you read the original I ask that you would consider looking at it again. https://decibel.ni.c.../docs/DOC-18320 Motivation for this presentation: At the 2011 CLA Summit, NI asked us what we thought was missing from LabVIEW or what needed to be improved. I already knew AQ’s position on this, but my feedback to NI and direct conversation with AQ was that I thought LabVIEW needed to be able to invoke methods on object references. At the time that I said this, I knew that I wasn’t 100% up to speed on all the different incarnations of by-reference architectures or messaging architectures out there, but I felt (and still feel) that this would be a powerful (if not necessary) evolution for LabVOOP in the future. Because I know AQ has spent a great deal of his life pondering these issues (for which I am extremely thankful), and discussing them with scores of brilliant minds around the world (like you if you're still reading this), I felt I needed to do some homework on these different techniques for achieving by-reference objects, if nothing else to improve my own skills, but also to strengthen or assuage my own position on this topic. The result is this presentation. Originally this presentation was merely a book report of by-ref OOP techniques that I was aware of. I tried to document the implementation details of each technique, the common pitfalls, and the pros/cons. Then I started providing justification for why by-reference support in any language was important, and demonstrated how problems that are simple to implement in other languages using pointers/references are more cumbersome in LabVIEW. Then I provided a summary of where I think LabVOOP could improve, what I’d like to see possible, and what we can do currently. Then I added suggestions on what methods and features by-reference classes might contain and how they can be implemented. Finally I provided recommendations for class authors to assist in the decision making of class implementation and utilization. I want to share this presentation with the LabVIEW community because I think there are probably very few resources like it available, a detailed discussion of object references in LabVOOP. If you find it educational or beneficial I would appreciate hearing that so I can get some validation on this topic for myself. To be honest this discussion is a bit out of my area of expertise, but I do think it’s an interesting topic and important to LabVIEW in the broader General Purpose Programming Language context. Thanks, Nate Edited October 13, 2011 by NATE Quote Link to comment
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.