The MacView

Virtual Instrumentation from a Mac perspective

Friday, March 24, 2006

Macintel and LabVIEW

I recently saw an article from a developer at Adobe about Adobe's Macintosh and the Intel switch. I found myself nodding a lot reading the article. However, I think they are in this position because they did not listen to Apple.

When we ported LabVIEW to Mac OS X (LabVIEW 7.0) we had several decisions to make. Should we keep LabVIEW CFM, or make it Mach-O (native binary format). CFM definitely would have been easier. It would have also given us a single binary that would work on Mac OS 9 and Mac OS X. We could have kept compiling with CodeWarrior (the IDE/Compiler of choice for many Mac developers at the time). It had a lot of advantages. If we would have gone with Mach-O, we would have access to new APIs and we would be following Apple's suggestions, but we would loose the fast compiler, excellent debugging environment and we would have to rebuild everything in Xcode (then ProjectBuilder).

A wise senior Mac developer here in the LabVIEW group decided we should bite the bullet and follow Apple's suggestion. We would not get rid of the CodeWarrior build, so when MetroWerks was able to come up to Xcode's level of Mach-O, we could switch back (which never happened).

LabVIEW has a very large Xcode project, so I feel the pain of the Adobe engineer. Xcode does not do well with large projects, although it has been making huge leaps in every release. Distributed compiles has made the slow compiles much more manageable. The editor is still slow. Link times will put you to sleep (zero link does not work well on large projects). It was painful, to say the least, but we did it because we trusted that Apple knew what they were talking about.

Adobe, on the other hand, did not listen to Apple (or maybe Apple didn't tell them?). Now they are faced with converting over to Xcode and getting everything Macintel ready.

There are a few things in LabVIEW that make it more difficult to port to another processor architecture. The biggest is probably that it is a compiler. When a VI is run, we compile the diagram to native code, and then run it. So for LabVIEW, it is not just a matter of recompiling, we actually have to change our code to compile native x86. One benefit we have, however, is the most of the hard work on that side was done with LabVIEW on Windows and Linux. All that was left was to massage the x86 code generator to follow Mac OS calling conventions, not trivial, but much easier than starting fresh on the compiler part.

We of course found all sorts of Endian assumptions in Mac specific code, along with all the other minor things that all need to be cleaned up.

The bottom line is that we listened to Apple and our transition is going pretty smooth. Adobe didn't and now they are paying the price.

Now what got us is that Apple led us to believe that consumer hardware would not be available until summer. When they sprung a January release on us, we had already set our release schedules. I imagine that is also the case with Adobe and Microsoft. Any project of that scale would be difficult to port, and moving the schedule up six months really causes problems.

Well, I just wanted to give another developer's perspective on the Macintel transition. I think this one is going very well, especially compared to the Mac OS 9 to Mac OS X transition.

[Corrected a statement about Apple's timeline for transitioning to Intel hardware 2006-03-24 13:29pm CST]

The views expressed on this website/weblog are mine alone and do not necessarily reflect the views of my employer.


Performance, Compatibility, Links and Aliases

I'd like to diverge a bit from the tips and tricks to talk a little about an issue I am running into in developing LabVIEW on Mac OS X. In the Mac OS Classic version of LabVIEW, we had a magic feature that would treat an alias anywhere in a path as transparent. This allowed you to move things around, replace the original with an alias and LabVIEW would treat the alias as the original item (folder or file).

When Mac OS X came along, we switched to Apples newer Carbon APIs that allowed for long file names. We also had a new type of alias, called a symbolic link (UNIX form of alias). So now, if an alias or symbolic link were to be anywhere in the path, LabVIEW should just treat it as the original item.

In LabVIEW 7.x, our first port of LabVIEW to Mac OS X, we found that we would handle symbolic links by accident, but aliases no longer worked. In LabVIEW 8.0, we added the ability to handle aliases as well, at a cost. Launch and Mass Compile, file intensive operations, were spending around 30% of their time in converting a path into a file system representation (FSRef). I reworked the code for the next major version of LabVIEW down to 7% to 9%. This has a noticeable impact on launch and mass compile, but I think we can do better.

Here are the options I am weighing:

1. By default, disable alias support (you can set a token in the preferences file to re-enable it).
2. Change the path format to UNIX style instead of HFS Mac Classic style
UNIX: /Users/johnd/Desktop
HFS: Macintosh HD:Users:johnd:Desktop

Option 1 above doesn't give us as much of a boost as I was hoping. Another Mac developer here would really like to see option 2 implemented. Here is the theory behind the possible speedup from option 2.

A large part of the time we spend in file operations is converting a LabVIEW path (HFS Classic Style) to a file system representation (FSRef) so we can call the OS APIs. If we instead just convert the path to a string (which is just one step in the HFS method), but a UNIX string, and then call UNIX APIs, the theory is that it will be much faster.

In order for this to be the most efficient, we would have to change the string format of paths on the Mac so they would be UNIX style instead of HFS Classic style. That means any string constants of paths would break.

So we are left to puzzle over compatibility vs performance trade-offs.

If you have concerns or an opinion on this post, please post a comment below.

The views expressed on this website/weblog are mine alone and do not necessarily reflect the views of my employer.


Friday, March 03, 2006

How to Create a Reference to Data

LabVIEW is, by design, a by-value language. You can modify the data on one wire, and as long as you fork that wire before the modification, the data on the other side of the fork will not be modified. There are a few places where this is not the case, but in general, that is how things work.

There are times when you would want a reference to the data instead of passing the data directly on the wires. One reason would be to be able to reference a change in data on a completely different VI (or block of a VI). This can be done now with globals or VI server references to controls.

Another reason to have references to data is to cut down on the possibilities of copies being made of the data. I've had LabVIEW run out of memory when I had a hash table (array of cluster of 2 strings) with some large strings in them, and a lot of them. This data type was being passed everywhere, and would eventually eat up all of the memory. It didn't have to, it wasn't that big, but it was just copied everywhere. All this copying also can effect performance.

You can use a global or even a VI server reference to a control, but then you have to know how many different sets of data you are going to have. There has to be a global or a control for each different set of data. It is also not very safe if you have a large data type. For instance, with my hash table if I where to get the value out of the global, modify it and put it back into the global, someone could have replaced the data in the global before I had a chance to put the data back, and I would overwrite the other change.

How do you overcome these issues? Queues (Functions Palette -> Data Communication -> Queue Operations). To create a new reference to data, drop an Obtain Queue primitive. This would be like the new operator in C++ or Java. It allocates space for your data. The only input you need to wire is the data type (and error in if you have one). Now this only allocates the space, it does not fill it in.

To set the value pointed to by your reference, drop the Enqueue Element primitive. Wire up the Queue reference wire, the error wire and then wire your data to put into the "Reference." The Queue Out terminal will now contain a Reference to your data.

There are a couple of important things to remember about this "Reference."

1. If you fork the wire, the data will not be copied. As long as the data stays in the Reference (queue), it will never be copied.

2. There is only one copy of the data. If you fork the wire and modify the contents of the queue, you have modified the value on the other side of the fork as well.

To modify the value pointed to by the Reference (queue), you need to Dequeue the data and then Re-Enqueue it. You will also do this to read the value. First, drop the Dequeue Element primitive and wire up the Queue and Error In terminals. Take the Element output and fork it out, modify it, whatever you want to do with the value in the Reference (queue). Then when you are done, drop the Enqueue Element primitive and re-Enqueue the data (original if you are reading, or modified value if you are modifying the value).

Dequeue and Enqueue give us a protection mechanism known as thread-safety. It prevents two people from reading the data, and then writing data over the results of one another. This is important in a data-flow language like LabVIEW, in which "threading" is transparently accomplished in the language itself.

Dequeue and Enqueue also do not copy the data, but rather move the data out of the queue. This is an important speed issues when you have large data structures.

Then when you are completely done with the Reference (queue), you use the Release Queue primitive. This will release any memory used to store the data and will cause all future uses of the Reference (queue) to return errors. This is the equivalent to delete in C++ (Java has no equivalent).

The views expressed on this website/weblog are mine alone and do not necessarily reflect the views of my employer.