Jump to content

DVR vs. Pointer


Recommended Posts

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.
  • Like 2
Link to comment

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.

Link

We 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 by Daklu
  • Like 1
Link to comment
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.

Link to comment

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 by Daklu
Link to comment

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 by jgcode
  • Like 1
Link to comment
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.
Link to comment

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

Link to comment

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.

Link to comment

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

Link to comment
  • 2 years later...

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.

Link to comment
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.

Link to comment

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 by NATE
Link to comment

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.

  • Like 1
Link to comment

Unless it's a reference - and even then it's still a *copy* of the reference :D

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.

Link to comment

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*

Link to comment
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 :D at the end of that sentance.

Link to comment

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. :thumbup1:

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...... :yes:

Link to comment

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. :D

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...... :yes:

Put on your thick skin; he can be really snarky.

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.