Popular Post Daklu Posted September 12, 2009 Popular Post Report Share Posted September 12, 2009 I know I'm way behind the rest of you on using DVRs, but today my quest for the Holy Grail (also known as Interfaces) hit another stumbling block. So all this time I've been playing around with DVRs I've been working under the assumption that the New Data Value Reference prim simply created a pointer to the object wired to its input. Based on some testing I did today it looks like the prim wraps the entire object in a DVR. The end result is that an object can be either By Ref or By Val, but apparently there is no way to have a reference to a By Val object that exists on the block diagram. For example, operations such as this will always create a copy of the object. Since the New DVR prim doesn't output an object is it really impossible to have a 'pointer' to an object? (Suddenly all the discussion about containing private data in a DVR makes much more sense.) [Rants follow... pay no attention to them. They are a result of my momentary frustration.] I was *this* close to having an easy-to-use Interface framework that would have worked with By Val objects and avoided the need to wrap the class data in a DVR. (I hate having to use initializer VIs just to get an object to work correctly.) I assume it was designed this way to try to prevent race conditions. I understand the reasoning but I still get frustrated by a development environment that at times seems unable to decide how much it supports references. Labview is kind of in a middle ground where it doesn't quite follow the pure "everything is data" idea but also doesn't fully support references, leaving it difficult to work with either. [/Rant] Back to the drawing board. I've been resisting the DVR data wrapping but it looks like I'll need to go that route. It's unfortunate--converting a class to implement Interfaces is more complicated if DVR data wrapping is required vs the method I had envisioned. [Edit Aug 2, 2010: Early non-working versions removed. Get released version here.] 3 Quote Link to comment
MikaelH Posted September 13, 2009 Report Share Posted September 13, 2009 Keep up the work on interfaces, and maybe we someday can convince NI (read AQ) to implement this for us I did have interface working in LV 7.1 on GOOP2, where we had to have out own dynamic dispatch code in our base class methods. Cheers, Mikael Quote Link to comment
Aristos Queue Posted September 14, 2009 Report Share Posted September 14, 2009 What you are asking for is not possible, at all, in a dataflow language. A raw pointer to data on a parallel by-value wire cannot be constructed meaningfully. Ever. That would completely destabilize LabVIEW's parallel architecture. The whole point of a data flow wire is that *nothing* other than the upstream primitive that created the value can modify that value. Nothing. If you have raw pointer access to the data on a parallel branch, you will almost always create wholly unpredictable behavior or a crash, with the crash being highly likely. Labview is kind of in a middle ground where it doesn't quite follow the pure "everything is data" idea but also doesn't fully support references, leaving it difficult to work with either. We fully support references. We do not support pointers at all. But you don't refer to a piece of the block diagram. You refer to an address in memory. That other, parallel wire, does not refer to that piece of memory. It refers to its own piece of memory. 2 Quote Link to comment
Daklu Posted September 14, 2009 Author Report Share Posted September 14, 2009 (edited) Keep up the work on interfaces, and maybe we someday can convince NI (read AQ) to implement this for us Thanks for the encouragement. For what it's worth I'm fairly confident Interfaces are on the roadmap somewhere. On another thread when I mentioned I'd really like Interfaces AQ responded with something along the lines of "not enough time has passed for that to be implemented." However, given the poor showing for Interfaces in the Idea Exchange and comments in The Decisions Behind the Design, I suspect they're much farther down the road than I would like. LinkWe have given strong thought to Java-style interfaces, which solve most of the real use cases for multiple inheritance. Even interfaces are a pretty advanced architecture choice for software, outstripping the understanding of just about everyone without some amount of computer science architecture training. As such, implementing them would be a low priority; we will want to focus on aspects that improve the fundamentals of single inheritance and encapsulation before we start expanding the power of the language again. Here's what's really puzzling about the whole thing: How did I manage to go two months without realizing that? Edited September 14, 2009 by Daklu 1 Quote Link to comment
Popular Post Daklu Posted September 14, 2009 Author Popular Post Report Share Posted September 14, 2009 We fully support references. We do not support pointers at all. In my experience the terms "reference" and "pointer" are often used interchangably. How are you differentiating the terms? They both essentially do the same thing--they refer to a location in memory that contains either the data of interest or the next memory location in the chain. You refer to an address in memory. That other, parallel wire, does not refer to that piece of memory. It refers to its own piece of memory. Having parallel wires refer to the same piece of memory is done all the time with refnums, be it named queues, events, fp controls, etc, so it's obviously possible. Branching a DVR results in two parallel wires referring to the same piece of memory. What you are asking for is not possible, at all, in a dataflow language. A raw pointer to data on a parallel by-value wire cannot be constructed meaningfully. Ever. Is this a limitation of dataflow languages in general or specific to Labview's implementation. Obviously there are still aspects of dataflow I don't fully understand. Data on the parallel by-value wire has to exist somewhere in memory. It don't get why a pointer to that memory location would not be meaningful. It would violate the principle of data flow, but there are lots of things within Labview that do that already. --------- Many Hours Later ----------- The whole point of a data flow wire is that *nothing* other than the upstream primitive that created the value can modify that value. Nothing. I had to look at this for a long time. When I first read it I envisioned all sorts of weird schemes under the hood that used the upstream prim to modify a value after it has been sent downstream. Here's a summary of the conversation I had with your alter-ego, Evil AQ (pronounced "evil-ack"), in my head. Evil AQ explained a lot of things to me, but I question his accuracy... you know, what with him being a figment of my imagination and all. (Note: My conversations with Evil AQ tend to be rather caustic.) Me: "Huh? The prim that created the value is what actually performs operations on the value downstream? That implies that rather than the data being passed around between prims, the data remains with the orignating prim and downstream prims are passed to it. How... odd. That seems kind of like functional programming. I thought you used C++ under the hood." Evil AQ: "No you dolt. That wasn't a comment about perpetually persistent primitives. Look, when have you ever seen a value on a wire change? Me: "I see it all the time. Wire an integer through an increment function inside a while loop. The value on the wire changes with each iteration." EAQ: "Let me rephrase... when have you ever seen a value on a wire change without that section of code executing again? Me: "Well, the value on the wire has changed after going through the increment node." EAQ: "Uh uh. That's a different wire. Another rephrase... when have you ever seen a value on any single wire, defined as all branches starting at the source terminal and ending at each destination terminal, change without that section of code executing again? Me: "Never. Duh." EAQ: "It's good to know you're not a complete maroon. This would be tougher to explain if I were restricted to finger paints and monosyllabic words." Me: "Monobillasic... monosymbolic... monosy... what?" EAQ: "Never mind. What would happen if you had a raw pointer to a value on a wire?" Me: "I could have Interfaces!" EAQ: "Slow down there Flash. Think about this for a minute. What would happen to the value on the wire?" Me: "Ohhhh... it would change." EAQ: "Very good. You pumping a full 100 Watts through that brain or is your dimmer switch turned up a little too high?" Me: "What about control refnums? They change values." EAQ: "There's a reason they're called 'control' refnums. They refer to controls, not wires. Control values can change. Wire values cannot." Me: "Oh. Right. Well queues can change the values of items that exist somewhere in memory. What about them?" EAQ: "Yes, but they are changing the value of an item the queue refnum refers to. They are not changing the value of the queue refnum itself, which is what actually exists on the wire." Me: "I see. But in reality by-val wires don't contain the data itself, do they? If they did memory allocations would take place at the moment the wire branches and I've know that's not right." EAQ: "Correct, wires do not contain the actual data." Me: "It actually refers to data that resides elsewhere in memory, right?" EAQ: "Sort of correct. Wires don't refer to a piece of data so much as they refer to a specific terminal on VIs and primitives. A terminal** is essentially something that can define the values of a piece of data, such as a control, indicator, constant, etc. and includes input and output terminals to prims." (**Evil AQ didn't know the correct terminology for this so he used the word 'terminal.' Luckily I knew what he meant.) Me: "So shouldn't it be possible to create a refnum that simply refers to the same memory location that a wire does? Ha!" EAQ: "Well yes, but it won't work in the way you're thinking. Try looking at it this way... by val data exists only in terminals. Wires are simply a graphical representation of how the terminals are mapped to each other. Ostensibly every time data "flows" across a wire a data copy is made and placed in the memory location that represents the destination terminal. While in principle you could have a refnum that refers to the same data the wire 'refers' to, effectively it would be just like a value property node for one of the terminals. (It doesn't matter which one, since they all contain the same data values.) That scheme would consume LOTS of memory, so the Labview compiler is smart enough reuse memory when it can. For example, since all the terminals a wire connects to must contain the same data values after executing, the memory locations that are represented by the terminals are actually pointers that refer to a single piece of data. Sometimes the memory location of that data can be reused by other terminals further downstream. Sometimes it can't. Ironically, obtaining a reference to that memory location is one way to guarantee it can not be reused." Me: "Really?" EAQ: "Well yeah. You're referencing the value in the terminal, which doesn't change until that section of code executes again. If downstream operations change the value a new copy must be made to avoid corrupting the old copy, which you have a reference to." Me: "I get it now. Thanks Evil AQ. You're the greatest." EAQ: "You sound like my mother. Now beat it punk, I'm trying to sleep." So did Evil AQ do a decent job of explaining things to me? 8 Quote Link to comment
Aristos Queue Posted September 14, 2009 Report Share Posted September 14, 2009 So did Evil AQ do a decent job of explaining things to me? Excellently done! I think I can add that the inplaceness algorithm -- where we decide a downstream wire can reuse an upstream wire's mem address and modify that value -- is logically equivalent to each wire having a reference count on that data, and when the ref count is one, the nodes can modify the address represented because no one is sharing it. If you created a DVR directly to a by-value wire's address, the refcount would not be one -- it would be two, one for the DVR and one for the parallel by-value wire. So until that other branch was done, you couldn't modify the data. A data copy is created so that the DVR is the one and only refcount for the data. There are various improvements we can sometimes do using top-swapping instead of deep copy, but in general, the DVR needs exclusive rights to that memory address. Quote Link to comment
Daklu Posted September 14, 2009 Author Report Share Posted September 14, 2009 (edited) Having only used imperitive languages other I still struggle to understand other programming paradigms. I've always envisioned the dataflow "magic" as being a function of the compiler. In other words, I expected compiled dataflow programs to be structured much the same way compiled imperitive programs are. The Wikipedia entry for Dataflow programming describes a very different structure: Dataflow programs are generally represented very differently inside the computer as well. A traditional program is just what it seems, a series of instructions that run one after the other. A dataflow program might be implemented as a big hash table instead, with uniquely identified inputs as the keys, and pointers to the code as data. When any operation completes, the program scans down the list of operations until it finds the first operation where all of the inputs are currently valid, and runs it. When that operation finishes it will typically put data into one or more outputs, thereby making some other operation become valid. Once I understood that the other pieces started falling in to place. Question for you: If I have a VI with independent parallel operations the execution order is indeterminate. Assuming compiled Labview programs use some sort of list scanning described above, does it follow that although I cannot predict which order each operation is executed the order will be the same each time the program is executed? [Edit: Is "terminal" the correct term to use when referring to something that can hold data? I've generally though of terminals only as controls or indicators that are on the conpane. Although constants and controls that are not on the vi conpane could be viewed as sub vis with their own conpanes...] Edited September 14, 2009 by Daklu Quote Link to comment
jgcode Posted September 14, 2009 Report Share Posted September 14, 2009 (edited) although I cannot predict which order each operation is executed the order will be the same each time the program is executed? I am pretty sure you can't - as this is where the dreaded race conditions come into play. I have seen this a bit from older code, that programmed with this not in mind (e.g. lots of globals)... It worked fine on their PC at the time. Move on to different PCs - multi threading, hyper threading, Core 2 Duo - where the timing characteristics of the code are now different and whammo - race conditions start popping up. Same code, different PC. Edited September 14, 2009 by jgcode 1 Quote Link to comment
Aristos Queue Posted September 15, 2009 Report Share Posted September 15, 2009 Question for you: If I have a VI with independent parallel operations the execution order is indeterminate. Assuming compiled Labview programs use some sort of list scanning described above, does it follow that although I cannot predict which order each operation is executed the order will be the same each time the program is executed? No. Individual branches you might be able to guarantee order of, such as where the output of node A forks to nodes B and C -- the compiler will always add those to the execution queue in a particular order. But if output of Node A forks to C and D, and B also forks to C and D, the order of C and D may vary depending upon whether A finishes first or B finishes first, and which finishes first can vary greatly depending upon the operating system thread scheduling. [Edit: Is "terminal" the correct term to use when referring to something that can hold data? I've generally though of terminals only as controls or indicators that are on the conpane. Although constants and controls that are not on the vi conpane could be viewed as sub vis with their own conpanes...] A "dataspace entry" is a single piece of LV data capable of going down a wire as a coherent LV data type. There are several dataspace entries that are not represented directly on the diagram, but, in general, any of the items you can interact with on the diagram are going to have a terminal representation at some point. Some things -- like a queue -- which aren't associated with any particular VI, have storage that is not directly a terminal. Quote Link to comment
Kurt Friday Posted September 15, 2009 Report Share Posted September 15, 2009 Cool thread I've been having a play with interfaces and I've got something fairly hacky that I thought I would throw up here. One of the problems that I was running into was the inability to call a dynamic dispatch vi using a CIN or a Run VI Invoke Node. What I was able to do is define a static dispatch vi for each class called Interface and in there I would have the methods that could be executed by the interface. I then define a set of vi's called interfaces that know what class they are opperating on and hence can call the appropriate static dispatch interface method using VI Server. I'm a bit green on interfaces so there may be big holes in this. The demo implements interfaces on two classes that are unrelated, a Thermometer and a DMM, the interfaces handle Create, Read and Destroy. InterfaceDemo.zip Quote Link to comment
Daklu Posted September 15, 2009 Author Report Share Posted September 15, 2009 Your implementation really deserves its own thread. Not because I'm worried about hijacking this thread (I think has run its course) but to make it easier to find. I like the way you've solved the problem of calling similar methods from unrelated classes. That is an approach I had not considered. I think I like that you don't have to obtain the interface before using it. On the other hand I think the reliance on strings and the CBR node will make maintenance more difficult in the long run. What you have doesn't quite present the user with traditional Interfaces, though it wouldn't be hard to wrap your interface in a class to give it a more standardized feel. Quote Link to comment
Kurt Friday Posted September 16, 2009 Report Share Posted September 16, 2009 Your implementation really deserves its own thread. Not because I'm worried about hijacking this thread (I think has run its course) but to make it easier to find. I like the way you've solved the problem of calling similar methods from unrelated classes. That is an approach I had not considered. I think I like that you don't have to obtain the interface before using it. On the other hand I think the reliance on strings and the CBR node will make maintenance more difficult in the long run. What you have doesn't quite present the user with traditional Interfaces, though it wouldn't be hard to wrap your interface in a class to give it a more standardized feel. Good idea, new thread over here Quote Link to comment
NATE Posted October 13, 2011 Report Share Posted October 13, 2011 Wow, thanks Daklu for this post. I know this post is 2 years old, but I was about to make the same mistake. Your Evil AQ explanation was a great revelation for me regarding the existence and limitations of references in a dataflow language. Really it just boils down to this: You cannot have both data and a reference to that data at the same time. When that happens, what you really have is a copy of the data (or an antiquated reference?). Changing the reference cannot change the data on the wire. Seems so simple and obvious now, but this distinction is not intuitive for those of us who have used references/pointers in other languages, and this distinction is very important to understand. I'll admit I haven't read all of the LabVIEW help on DVRs, is an explanation like this given in the LabVIEW help? Specifically that branching a wire into a DVR Create node will always create a copy of the data? That's counter-intuitive to what I expect when I think of creating a reference. Quote Link to comment
crelf Posted October 13, 2011 Report Share Posted October 13, 2011 Specifically that branching a wire into a DVR Create node will always create a copy of the data? That's counter-intuitive to what I expect when I think of creating a reference. Well, it really is intuative, as you're creating a reference on a wire that is already a copy of the original: the bottom line is that the copy is created when you branched the wire - even before it went into the DVR create. That's why you have a reference to a copy - because that's what you actually asked it to do. Quote Link to comment
NATE Posted October 13, 2011 Report Share Posted October 13, 2011 (edited) Yes, but we all mentally compile that out of our thinking. We know that LabVIEW will only make a copy of the data when it needs to, so the part that is not immediately intuitive to me is that fact that wiring the branch to the Create DVR primitive is one of the rules LabVIEW uses to determine that a copy is required. I admit that it is internally coherent when you stop and really think about what pure dataflow is, but even though I've been using LabVIEW for 12 years I haven't really pondered all of it's finer implications. The majority of dataflow is very intuitive, that's why I love it. Edited October 13, 2011 by NATE Quote Link to comment
asbo Posted October 13, 2011 Report Share Posted October 13, 2011 Yes, but we all mentally compile that out of our thinking. We know that LabVIEW will only make a copy of the data when it needs to, so the part that is not immediately intuitive to me is that fact that wiring the branch to the Create DVR primitive is one of the rules LabVIEW uses to determine that a copy is required. I don't disagree with you, but perhaps we're getting ahead of ourselves with that mental compilation. What a branch in a wire means, literally, is that the same piece of data needs to go to two places. What's the simplest way of accomplishing this? Copy the data. In reality, LabVIEW will optimize as much as possible of this copying, but fundamentally every wire branch should be viewed as copying data. 1 Quote Link to comment
crelf Posted October 13, 2011 Report Share Posted October 13, 2011 In reality, LabVIEW will optimize as much as possible of this copying, but fundamentally every wire branch should be viewed as copying data. Unless it's a reference - and even then it's still a *copy* of the reference 1 Quote Link to comment
asbo Posted October 13, 2011 Report Share Posted October 13, 2011 Unless it's a reference - and even then it's still a *copy* of the reference That's a good clarification - programmers that have never had the pain/joy of working directly with pointers may not realized that branching a reference wire does not clone your TCP socket/VISA session/front panel control, it just lets you operate on it in more than one place simultaneously. Quote Link to comment
Daklu Posted October 13, 2011 Author Report Share Posted October 13, 2011 Wow, thanks Daklu for this post. I know this post is 2 years old, but I was about to make the same mistake. Your Evil AQ explanation was a great revelation for me regarding the existence and limitations of references in a dataflow language. I'm glad you found it helpful. (At the risk of dislocating my shoulder patting myself on the back, out of all the posts I've made that is one of my favorites.) Specifically that branching a wire into a DVR Create node will always create a copy of the data? That's counter-intuitive to what I expect when I think of creating a reference. I agree it's not the behavior one would expect coming from a more traditional language. My confusion came from the "reference" part of Data Value Reference and the ambiguity between "pointers" and "references" in common programming languages. Mentally I now think of DVRs as a form of boxing/unboxing. In reality, LabVIEW will optimize as much as possible of this copying, but fundamentally every wire branch should be viewed as copying data. Depends on what programming role you're playing at the moment. For the "business data flow designer," yes. For the "application optimizer," maybe not. But I agree most of the time every wire branch should be viewed as a copy. Unless it's a reference - and even then it's still a *copy* of the reference Isn't a reference simply another piece of data? It's not business data (stuff the end user cares about,) but it is information that needs to be managed within the application. That's how I look at it anyway. *shrug* Quote Link to comment
crelf Posted October 13, 2011 Report Share Posted October 13, 2011 Isn't a reference simply another piece of data? It's not business data (stuff the end user cares about,) but it is information that needs to be managed within the application. That's how I look at it anyway. *shrug* Yes, that's why I added the at the end of that sentance. Quote Link to comment
Val Brown Posted October 16, 2011 Report Share Posted October 16, 2011 So, if I've followed ALL of this, what we come down to is: LV is a pure dataflow language with an implementation of references that is inherently dataflow and not raw pointers. Obvious when you "read the manual" and such but that option is TRAINED OUT by traditional CS approaches based on text-based "jump anywhere" languages that allow for raw pointers and, thereby, leave the programmer with the mess of memory management, ah, I MEANT to say: with all of the options of deciding how to manage memory everywhere still available to you, along with the responsibility for actually doing ALL of that. In other words, coming from a traditional "jump anywhere" architecture being ASSUMED, LV can seem quite "limited". Recognizing what it actually IS, LV is an inherently elegant implementation of dataflow. Hmmmm, maybe I should ask Evil AQ about all of that...... Quote Link to comment
Daklu Posted October 16, 2011 Author Report Share Posted October 16, 2011 LV is a pure dataflow language with an implementation of references that is inherently dataflow and not automatically manages access instead of allowing raw pointers. There. Now I agree. Whether or not G follows "pure" dataflow is--as far as I can tell--still an open question because "dataflow" itself may not be well defined. The academic CS community defines it as meaning all effects are local. AQ borrowed the term "hygienic" from functional languages: "A VI should be considered clean if, when executed, for any set of input parameters, the output values are always the same. In other words, if you have a function that takes two integers and produces a string output, if those two integers are 3 and 5 and the output is "abc" the first time you call the function, it should ALWAYS be "abc" every time you call the function with 3 and 5. The term for such functions in other languages is "hygienic functions." It's a term I'd heard before but never thought to apply back to LabVIEW." By that definition I would think a pure dataflow language would not allow any non-hygienic functions. Clearly it is possible to create non-hygienic functions in G; therefore it is not pure. It makes much more sense to me for "dataflow" and "hygienic" to be orthogonal ideas rather than synonyms. (I dare challange acadamia? *gasp*) In my head "dataflow" refers to how code is sequenced at runtime while "hygiene" refers to the function's input/output predictability. Dataflow allows us to easily write multithreaded applications. Hygienic functions allows us to easily verify it's behavior. By this definition G is pure dataflow but not purely hygienic. Hmmmm, maybe I should ask Evil AQ about all of that...... Put on your thick skin; he can be really snarky. Quote Link to comment
Val Brown Posted October 17, 2011 Report Share Posted October 17, 2011 Yes I guess Evil AQ can be snarky.... So again, from my perspective LV is a pure dataflow language but may be purely hygienic. ooops I meant may NOT be purely hygienic..... Quote Link to comment
crelf Posted October 18, 2011 Report Share Posted October 18, 2011 Clearly it is possible to create non-hygienic functions in G... How? I'm interested in hearing the options... Quote Link to comment
Val Brown Posted October 18, 2011 Report Share Posted October 18, 2011 How? I'm interested in hearing the options... Yes so am I. That's why I said may.... 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.