Wednesday, April 27, 2005

Oops. My mistake...

In a previous post on threading I gave a high-level pseudocode description of a multithreaded MPEG decoder.

It was wrong.

A revised (and, I hope, more correct) version is:

The new high level design for video looks like:

  • Accept input, and separate into VOBs
  • Hold VOBs for processing [MT]
  • Pick a VOB and demux its content into substreams
  • Queue packets for decoder(s)[MT]
  • Decode stream into buffer in decoded VOB
  • Wait for VOB completion [MT]
  • Hold completely decoded VOBs[MT]
  • Get next VOB and deliver decoded substreams to presentation engine.
  • Hold decoded, substreams for presentation [MT]
  • Mix and present decoded content.

* * *
My first instinct was to simply edit the original post and replace the incorrect "code" with the corrected version.

Then I remembered the programmer's diary, I mentioned in another post.

One of the hardest parts of becomming a good programmer is learning how to deal with mistakes. First you have to accept that you and the people you work with are going to make mistakes. Then you have to train yourself to react positively to your own and other peoples' mistakes.

Reacting positively to your own mistakes means you fix your mistakes. You don't hide them. You don't defend them. You just fix them -- and clean up any consequences resulting from the mistake.

Reacting positively to other peoples' mistakes means you bring them to their attention in a non-threatening way. You don't fix their mistakes for them (at least not silently.) You don't help them hide their mistakes. You don't gloat over their mistakes (although it's hard to avoid a certain level of "boy, I'm glad I didn't make that mistake.) What's important is that the person who makes the mistake learns that it happened, and that the mistake gets fixed.

And finally, when someone brings one of your own mistakes to your attention, the only proper response is "Thank you." After saying that then you can proceed to analyze the report to see if it's correct, but first you must reward the person who respected you enough to tell you about your (possible) mistake.

A lot of this comes from another landmark book about software development: The Psychology of Computer Programming, by Gerald Weinberg.

* * *
I predict that we will never have a good programmer as president of the United States (and vice versa.)

* * *
So why did I make this mistake? Because I was thinking about multithreading on a frame-by-frame basis. Then when I switched to thinking about it on a VOB-by-VOB basis I didn't completely reset my mental model of the problem.

How can I avoid making this kind of mistake in the future? (Or how can I make it less likely to happen?) Tough question -- maybe awarness of the potential pitfall will help.

Thursday, April 21, 2005

Baby Geese and Parrots

The eggs in the goose nest right outside the door to our building here hatched the other day. Within hours after they hatched, they were cute bundles of fuzzy yellow feathers running around on their own -- much to their mother's dismay. A day later the parents marched their goslings over to a near-by lake.

How come baby geese are so competent and cute when baby parrots are totally helpless and look like something from a grade C SF flick?

For example

My theory is that baby parrots are too busy growing an intellegent brain to have any energy left over for cute. Geese seem to make-do without benefit of brain.

Tuesday, April 19, 2005

Parallel by force

Suppose you've created the multithreaded MPEG decoder as outlined in the previous entry. Remember the good reason for multithreading the MPEG decoder was:
  • The task is inherently multithreaded so a multithreaded solution results in simpler code.

In fact the MPEG decoder almost begs to be multithreaded.

So one day your multithreaded MPEG decoder is happily zipping thru an MPEG stream that contains just video and one audio track. The following threads are running:

#1 Accept and demux input
#2 Decode video substream
#3 Decode audio substream
#4 Mix and present substreams.

Then your boss shows up and says, "I spent all this money on a 16 CPU superserver and your application is only keeping it 25% busy. I want you to increase the parallellism so all the CPU's will be kept busy. NOW!"

* * *
Now what do you do (other than looking for a new job with a new boss.)

You've already added the "natural" multithreading that is inherent in the problem. How can you increase parallelism even further?

It's time to try to apply the other good reason.

  • The task can be cleanly decomposed into multiple sub-tasks that are highly independent; the independent tasks can use resources in parallel; and the benefits of this parallel usage outweigh the overhead of multithreading. (All three conditions must be true.)

Hmmm....

A video stream is a series frames. Maybe we can create multiple threads and have each thread decode a separate frame. So we add component that separates the stream into a series of, undecoded frames (yes this is fairly easy to do without actually decoding the frames) and a pool of threads that processes these frames. Each thread from the pool picks up the next un-decoded frame, decodes it, and adds the result to a collection of decoded frames. Since frame-decode time varies as a function of the complexity of the image, we also need component to shuffle the decoded frames back into the correct order.

Voila, we can keep as many CPU's busy as we want to by looking forward far enough. Makes sense, right?

Nice theory, anyway. When you start coding the frame decoder, you'll quickly run into a major stumbling block. One of the techniques MPEG uses to compress the video image is to send most frames as a diff from the previous frame. This is very effective -- especially when the movie is showing relatively static scenery (it doesn't work so well during explosions.) Thus as you decode frame #n you regularly have to refer back to frame #n-1 to apply the diff and thereby create the final result. Even more interesting, sometimes you have to look *forward* to frame #n+1! (Don't ask, the MPEG folks are a twisted bunch.)

So the thread-per-frame solution sounds plausable (you can probably sell it to your boss) but fails the "independance" test. Back to the drawing board.

Fortunately for DVDs there's another approach. In order to support fast forward, slow motion, jump to scene, etc, the video on a DVD is carved up into chunks called video objects (VOBs) A VOB contains about half a second worth of video, audio, subtitles, etc. and what's more important each VOB is independant of the VOBs that preceed it and follow it. So, although the thread-per-frame idea was a bust, a thread-per-VOB approach will work nicely. You may need a priority scheme to insure that the thread that's decoding the VOB scheduled to show up next on the screen gets all the resources it needs, but other than that you've found a clean division of the main task into subtasks that can take advantage of the available CPU's by running in parallel.

The new high level design for video looks like:
  1. Accept and demux input
  2. Queue packets for decoder(s)[MT]
  3. Separate into VOBs
  4. Hold VOBs for processing[MT]
  5. Decode VOB
  6. Hold decoded VOBs for reordering[MT]
  7. Reorder decoded VOBs into decode stream
  8. Queue decoded streams for mixer.[MT]
  9. Mix and present substreams.

This approach has added some more synchronization spots -- one to hold the separated VOBs waiting to be decoded, and one to hold the decoded VOBs until they can be placed in the correct sequence and passed on to the mixer. It might be tempting to try to merge demuxer with the VOB separator or the decoded VOB holder with the decoded stream queue, but don't give in to temptation. Solve one problem at a time and let the inherent parallelism take care of improving performance. [or at least get it working correctly and profile it before optimizing.]

The moral of the story:
  • Finding the right decomposition into independant subtasks needs to be done carefully based on detailed understanding of the domain. An obvious solution may not be the right solution.

Monday, April 18, 2005

Multithreading: Why bother?

So multithreading synchronization is hard and requires hardware support. How do all those existing multithreaded programs manage to work?

Answer #1 Someone got lucky. Doesn't it comfort you to know that the software flying your airplane might be working by accident?

Answer #2: To write thread-safe code you have to follow a different set of rules. Actually an additonal set of rules, because all the old rules for writing good programs still apply.

Since single threaded code runs faster, is easier to write, and is easier to test than multithreaded code, why anyone would willingly go to all the effort necessary to write multithreaded code? Good question. The first decision that needs to be made when designing a multithreaded program is, "is this necessary?" If you can't come up with a compelling benefit for multithreading, go for the simple solution.

There are lots of bad reasons for multithreading, and only a couple of good ones. The good reasons I know of:

  1. The task is inherently multithreaded so a multithreaded solution results in simpler code; or
  2. The task can be cleanly decomposed into multiple sub-tasks that are highly independent; the independent tasks can use resources in parallel; and the benefits of this parallel usage outweigh the overhead of multithreading. (All three conditions must be true.)

Let me provide an example of the first case.

MPEG is a standard for encoding audio-video information. A stream of MPEG encoded data can contain many substreams. For example: an MPEG encoded movie recorded on a DVD might contain a single stream of video, two or three streams of video overlay (the subtitles in various languages); several streams of audio (the main audio track in different languages, etc. and the director's comments); and DVD navigation information to support fast forward, fast reverse, etc.

These substreams are multiplexed at a packet level. The overall data stream consists of a set of fixed-sized packets and each packet is part of a a particular substream. You could have a navigation packet, two video packets, and audio packet, another video packet, a subtitle packet, and so on.

The substreams themselves have a rich internal structure. For example the video stream contains sequences of variable bit-length, huffman encoded data fields. Suppose the video stream decoder has extracted the first five bits of an eleven bit field when it hits a packet boundary, it would be a nightmare to attempt to save the video-decoding state including the partially extracted field, and switch to a completely different context in order to be able to properly decode the audio packet that comes next.

Splitting the MPEG decoder into a main demultiplexing thread and independent decoding threads for each substream, and a mixing thread to manage the simultaneous presentation of the decoded threads dramatically simplifies design.

It is interesting to note that there are two synchronization hot-spots in the multithreaded version of the MPEG decoder. One is the point at which the demultiplexer passes a packet is passed to the specific stream decoder for this type of packet, and the other is the point at which the mixer accepts the decoded substreams for integration and presentation. Everything between these two points can and should be coded as if the program were single threaded.


These synchronization hot spots should be separate components. A possible high level design would be:
  • Accept and demux input
  • Queue packets for decoder(s)[MT]
  • Decode substream
  • Queue decoded streams for mixer.[MT]
  • Mix and present substreams.

Multithreading issues should addressed only in the two components marked [MT]. Everything else should be written as if it were single threaded (and protected accordingly.)

Friday, April 15, 2005

The moral equivalent of a mutex

In yesterday's post I used the phrase "The moral equivalent of a mutex." I claimed that it was not possible to write code that shares data between threads safely without one.

This prompted an anonymous response which cited Dekker's algorithm as an example of a software-only synchronization mechanism. I appreciate the response (even though I immediately rebutted it) because it prompted a train of thought about what the "moral equivalent..." is and why multithreaded code is so falupin' hard.

Mutex equivalents on Win32 include: CriticalSection, Event, Mutex, Semaphore, InterlockedIncrement, InterlockedDecrement, InterlockedExchange, and so on... Other OS's support some of these and have their own, unique variants with various degrees of arcanity (SYSV Semaphores, for example.) The point is that all of these objects are designed specifically to address thread synchronization.

Dekker's algorithm is interesting because it is an algorithm for implementing a critical section. I'd count it as the moral equivalent... with one caveat. It doesn't work unless there is an underlying hardware synchronization mechanism.

The algorithm contains the following code:
 
flags[i] = BUSY;
while(flags[j] == BUSY)
<SNIP>
<if you get here you have access to the resource>


The problem shows up in the following sequence of events:

Thread 0: flags[0] = BUSY;
Thread 0: while(flags[1] == BUSY) // false so thread 0 has access
Thread 1: flags[1] = BUSY;
Thread 1: while(flags[0] == BUSY) // flags[0] from cache is still FREE
// so the condition is false and thread 1
// also has access to the resource


I'm not saying that Dekker's algorithm is wrong. I'm saying that it contains an underlying and invisible assumption about how things are executed in the computer. In particular it assumes that operations on shared memory are atomic and immediately visible to other threads. If that assumption is correct then the algorithm works. Thus the algorithm reduces the problem of providing CriticalSection behavior to the problem of implementing the shared property.

* * *

A programmer reading code, has a mental model of how the machine works. Most of the time we use a very simple model -- things in our mental model happen sequentially in the order that they appear in the source code we are reading. Having this simple model is A Good Thing[TM] because it allows us to concentrate on what the program is supposed to be achieving rather than how it goes about achieving it.

The problem with this simple model is performance. The code may say:

for(int i = 0; i < 10; ++i)
{
someFunction(i * k);
}


but the compiler may generate code that looks more like:


int j = 0;
do
{
someFunction(j);
j += 10;
} while (j < 100);


on many processors a literal translation of the second version will be faster than a literal translation of the first version -- so the language standards committees have given compiler writers freedom to provide the second version as a legal compilation of the first code.

If you observe the state of the system before this code executes, and after it completes, you can't tell the difference between the two versions. The only observable difference is that one version runs a bit faster.

The programmer gets to write code in a way that describes the algorithm most clearly (in his mind, anyway), and the processor gets to execute code that generates the desired result faster. Everybody is happy.

* * *

Multithreading changes the rules. Rather than observing the before and after states of the system, you now have to be concerned about every intermediate state that might be visible to another thread. A lot of discussions of multithreading present C source code and discuss the implications of an interruption occurring between each statement. The discussion of the incorrect algorithms that precedes the presentation of Dekker's algorithm uses this technique to identify the points of failure. This is a step in the right direction, but it's still not good enough.

Consider the following statement:

volatile i;
a[i] = b[i] + c[i];

and what happens if "i" is potentially changeable by an outside agency (another thread, a memory mapped I/O, etc.) For example, suppose that before executing this statement i has the value 0, but sometime during the execution of the statement i takes on a value of 1. How many possible outcomes are there for this statement?

The answer surprises many people. There are 8 possible outcomes because the compiler is free to evaluate the three instances of i in any order it chooses to. To analyze an algorithm containing the above statement in a multithreaded environment you must consider all eight of these cases.

So all we need to do is break each statement down into phrases that can occur in arbitrary order and analyze the effect of an interrupt between any two phrases. Are we there yet?

Well, it depends on how many phrases you see in the following statement:

++j;

Assuming int j;, this probably compiles into a single machine language statement: inc [j] -- hence one phrase, right?

Nope. At this microcode level, this statment says: retrieve the value of j from memory; add one to it; store the new value back into memory location j. That's two phrases (why not three? because "add one to it" is internal to the processor and therefore invisible to other threads.)

So, we've gotten to the microcode level. We must be at the right level of analysis by now.

Sorry, to truly understand you have to throw in instruction pipelining, and cache (remember cache.) Once you take them into account, then you model of what really happens in the machine is complete enough to analyze the thread-safeness of the probram.

Alas, pipelining and caching issues are truly beyond the control of the programmer, so the problem of ensuring thread-safeness appears to be unsolvable.

Except!

Thank goodness there's a way to tell the hardware to switch momentarily from its default anything-for-speed mode into a safe-for-the-silly-programmer mode. Every processor has at least one synchronization operation that does things like flushing and/or updating cache, locking the bus for a read/alter/rewrite cycle, etc. These operations tend to produce a dramatic slow down because they defeat all the work that went into designing a cache and a pipeline, etc to speed things up. The other problem is on many CPU's the hardware guys decided that these operations should be reserved for kernel mode, so enlisting the hardware's help may involve an OS call with the corresponding high-overhead context switch.

In any case, I think this justifies Rule #1: Multithreading is hard.

Thursday, April 14, 2005

Multithreading considered

Peter said I should post this, so....

Hi Peter,

On 4/14/05, Peter Wilson wrote:
> Do you know of any books on threading in software design written at
> the level of Design Patterns?

Sounds like a great book. I want a copy! 8-)

There have been some interesting articles recently in C++ journal, but I haven't seen any of the "newer thinking on threads" gathered into a book.

This is going to become more critical RSN as the multi-core chips hit the market. Maybe I should write a book!

Rule #1: Multithreading code is hard.
Corollary: If you don't think it's hard, your code is wrong! (witness Java synchronized)

Rule #2: If the hardware isn't involved at some point, it's wrong.
There are no software-only synchronization methods. This doesn't mean you have to lock a mutex every time you touch shared data. It just means that somewhere in any thread safe technique there has to be a mutex (or the moral equivalent.)

Rule #3: Don't try to cheat -- particularly not for performance sake.
Multithreading buys you performance through parallelism, not
through shoddy coding techniques. (remember the Double Checked
Locking Pattern? (and see my blog for TCLP))

Rule #4: You need a model.
If you wing it, or play it by ear, you'll get it wrong (I'll put money on it.) Separate the thread-safeness from everything else and get it right in isolation. Then use encapsulation to keep "the next guy" from cheating.

Rule #5: Testing multithreading code is harder (and more important) than writing it in the first place.

how'm I doing?

Dale

Wednesday, April 06, 2005

Joel on Hungarian

I just got around to reading the third installment of Joel On Software's essays on the new FogBugz release.

In it he extolls the virtues of Hungarian notation. I was somewhat taken aback, since Joel usually makes so much sense and Hungarian is such an abomination, but then I noticed the context.

Hungarian notation was originally developed to overcome a deficiency in the C language and in C compilers -- weak type checking. Using HN you could do the "type checking" by eyeball rather than relying on the compiler. Once the language and compilers got smart enough to complain when you tried to assign the address of a SnaggleWhomp to a pointer to DeedleBlang then the justification for Hungarian disappeared -- leaving only it's significant drawbacks. artThe adjMost advImportant prepOf adjithinkThese nounsubjDrawbacks verbWas adjUnreadable nounobjCode.

However, the reason Joel gives for valuing Hungarian is that the home-grown Thistle compiler they use at Fog Creek has trouble compiling VB Net without it. Aha-- once again you have a defective language and a deficient compiler to compensate for and Hungarian rides again!

Tuesday, April 05, 2005

Supercomputers and tapestry weaving

There's always been a strong link between computers and weaving, but a recent New Yorker article looks at the relationship from a different perspective.

It's a long article so don't worry that the computers don't show up for a while.

Thursday, March 31, 2005

Hybrid User Interface

I really like my new Escape Hybrid, but I've started to notice some interesting UI issues:

I was stopping at a traffic light yesterday. I'd been driving a while so everything was warmed up. The gas engine turned off as I dropped below 20MPH -- as expected.

If the radio's not on it gets eerily quiet when you stop. Cool.

Then I took my foot off the brake. I noticed something unexpected. The car started to creep forward, just like "normal."

Hmmm...

In a normal car, the creep happens because the gasoline engine has to keep running. The torque "leaks" through the automatic transmission's torque converter. But for a hybrid the gas engine is off, and an electrical engine doesn't really need to keep spinning. In fact I'll bet the electrical engine was at a dead stop, too, when my foot was on the brake. Where is the creep coming from?

I'm betting that it's designed into the system to comfort those of us used to an automatic transmission. It reinforces the concept that a hybrid is "just like a normal car, only more efficient."

I wonder how much time Ford wasted getting this behavior to feel right. Personally I'd just as soon my car stayed where I put it unless I explicitly tell it otherwise.

Tuesday, March 22, 2005

Cross-Programmer Code

A lot of my programming work is intended to be portable across platforms where a platform is defined as a combination of operating system, computer architecture, and development tool set (compiler, etc.). ACE is a prime example of what it takes to achieve this goal.

However,

Even more important that platform portability is programmer portability. It is highly unlikely that any significant programming project will be developed and maintained by a single programmer for the life of the project. Every time a new programmer gets involved in a project the source code has to be "ported" into that programmer's model of the language.

Every programmer carries around a lot of mental baggage. Some of us are fresh-out-of-school apprentices -- lacking the pragmatic experience of a seasoned pro. Some of us are old fogies with fond memories of FORTRAN COMMON (who strive to recapture the glory using the Singleton pattern (chuckle.)) Some of us have been programming in C++ so long that we forget how arcane some of the "obvious" idioms are.

Fortunately, unlike computer architectures, compilers, etc. the port can work both ways. The code can be adapted to the understanding of the new programmer, or the new programmer's understanding can be adapted to the code. In fact there is usually much more of the latter adaptation than the former, although I have certainly been involved in situations in which it was easier to rewrite the code than to attempt understand it.

Recognizing how often programmers must adapt to unfamiliar code, and vice versa, we should make an effort to write programmer-portable code. With that in mind, I propose the "five programmer test."

Given a language feature or coding idiom, create a sample of code using that technique.

Select five programmers with skills ranging from average to superstar (below average programmers should be dumped on someone else's project.) Ask each of them to explain in English what the code does and to describe any limitations, consequences, etc. that need to be considered when using the technique.

If all five of them agree, then it's ok to use the technique.

If at least three of the five agree (and one of them is the superstar) then it's ok to use the technique, but it requires a comment to clarify the usage.

If fewer than three programmers understand the technique, or if any programmer "understands" the technique, but her explanation of what it does is way off base -- find another way to achieve the same goal that does pass the five-programmer test.

Tuesday, March 15, 2005

Another Interview Question

Another good interview question is, "Once you fix all the syntax errors and get a clean compile, what type of errors are most likely to still be in your code?"

Wrong answer: "None." End of interview. Have a nice life.

Most common answer: "I don't know."

Followup question: "So how could you find out?"

When I first asked myself this question (shortly after reading Writing Solid Code) my solution was to create a "programmer's diary." This was a background program that I could pop up with a hot key. It opened up an edit window into which I could paste or type information. It date/time stamped the entry then appended it to a sequential file and disappeared.

To use it, I'd select/copy code containing an error, pop open the edit box and paste it, then annotate it to explain the error. I did not do any further analysis in-line. Instead I went back to whatever I was doing -- fixing the problem or running more tests or whatever...

After capturing data for about a month, I analyzed the file. I categorized the types of errors into classes like:
  • uninitialized or improperly initialized variable;
  • sense of a condition is backwards;
  • failure to release resource when returning from a function;
  • difficult to use, difficult to understand, or easy to break feature of the language (think "goto" although I'd already stopped using those.)

Then for each class of mistakes I asked myself:
  • What can I change can I make to my coding style or work habits to prevent this type of error?
  • What can I change can I make to detect this type of error sooner?
  • What type of test would detect this type of error?

In some cases this resulted in changes in my coding style. For some cases I added new types of tests to my set of tools. In others, just the increased awareness of my error-of-choice was enough to help me avoid the error.

I continued to use the diary for a couple of months afterwards, and yes, there was a noticable reduction in the types of errors I had specifically targeted. Without benefit of statistical analysis, I also think there was a significant overall reduction in uncaught-by-the-compiler errors.

The downside of all of this is when I get into an argument (oops, I mean a reasoned discussion) about programming style issues I tend to be dogmatic about my style. That's because I think it's based on emperical evidence rather than aesthetics or arbitrary preferences. This would be a lot more valid if I had used the diary recently. My emperical evidence is from sometime before 1995 -- and of course it is specific to one programmer. Programming has advanced considerably since then -- in particular exceptions and patterns like RAII have changed the way I program. I wonder if it's time to fire up the old diary program.

Saturday, March 12, 2005

This one's for Jonathan

In the lobby where visitors sign in at Google, there's a Naked Juice vending machine.

The Computer History Museum

We visited the Computer History Museum yesterday. Lots of interesting artifacts including an Enigma machine, a Cray 1, etc. The item that captured my attention, though, was a white teapot. In fact, THE white teapot. If you've seen the test images from any 3D graphics program, you already know what the teapot looks like. And it does.

Thursday, March 10, 2005

Do you know the way to San Jose?

Do airplane trips ever just work? I mean have you ever gotten to the airport on time, sailed through security, found a comfortable seat next to a pleasant companion, arrived at your destination feeling refreshed and rested, and had your luggage make it, too?

Well...

Tina and I just flew to San Jose to visit Peter, and the perfect plane trip didn't happen (one more time.)

Let’s see,

  • My computer fell out of the back of Dave's van as he dropped us off at the airport (nothing damaged, apparently.)
  • When we checked our bags, they took my picture ID and wandered off with it. When I asked what was going on, they told me my name was on a "watch list." This must be the same evil Dale Wilson who is behind on his alimony payments and stiffed an eye-care place somewhere in Illinois. Kind of spooky knowing I have an evil twin.
  • Next was Tina's turn with security. "Is this your bag, ma'am?" said the TSA guy. "Yes," replied Tina. "I'm going to have to ask you to open it up." By then I was through security myself, so I didn't hear the details, but after I stood around for quite a while, Tina finally made it through. Apparently they confiscated a vicious looking nail file and were deeply suspicious about the dangerous drugs that were Not In Their Original Ibuprofen Bottles!
  • So the plane was a little bit late taking off.
  • And we landed in Tulsa. Most of the passengers go off, but those of us who were heading on to Phoenix on the same flight rearranged ourselves and got comfortable for the next leg. One of the stewardesses stopped by to admire my beard. She said her husband also had a long beard, and was giving Tina some really unfortunate suggestions involving braids and tiny lights, when the PA system announced we all had to get off the plane and go to another gate. There was vague mention of a "maintenance issue."

    When we got to the new gate, there was an enormous crowd. Much more than a planeful. It seems that they were putting us on a plane that was supposed to go to Dallas. Since the plane was going to Phoenix instead, now, the Dallas people were being sent to yet-another-gate where presumably they would eventually be put on their own replacement plane to replace the one we had just borrowed from them.

    This being Southwest, we were given new boarding passes that let us board first -- even before "families traveling with small children." If you want to get on a Southwest airplane first, get yourself a mauve boarding pass (honest they called it mauve!)
  • So that got us on the plane in Tulsa. We were 45 minutes late, but we were on our way. Nothing much went wrong during the flight to Phoenix (unless you count peanuts, but they're normal for a Southwest flight.) As we made our final approach into Phoenix they announced that Southwest was holding all connecting flights, and would anyone going to San Jose go directly to gate C2. Of course, our plane docked at the far end of the D concourse, but it coulda been worse. We hiked on over to C2 in time to walk right onto the plane shortly before they closed the doors. Things were looking up -- this plane was half empty so we had no problems finding seats.
  • Oops. Spoke too soon. As we arranged ourselves I realized that I had left the book I was reading on the other plane. "Do you think they'll let me go back and get it?" I asked Tina. Right.
  • So on to San Jose. We made up the time somehow and actually got to San Jose on time! The only unfortunate part of this leg was when the stewardess decided to sing as we taxied in to the airport. I guess it was endearing. A personal touch sort of like the early episodes of American Idol. "A bit pitchy," said Randy, "but not too bad." "I don't think this was a good song selection for you," said Paula, "but you've got a, um, loud voice." "I've heard better performances from the taxi driver that brought us to the hotel." said Simon.
  • Home free, eh? Our baggage made it! Calloo, Callay!
  • Then we got to Peter's car, and I said -- where's my computer? The brief case containing my computer--which I had carried on, so I couldn't blame the airline--wasn't there! In near panic I headed back to the baggage claim area. Fortunately it was sitting on a chair waiting for me. Airport security hadn't confiscated it as an unaccompanied bag -- probably because there was a woman there watching it. She said she saw us leave it and couldn't figure out what to do, so
    she was waiting a bit to see if we came back before calling security. Thank you, thank you, thank you.

      And so we're in San Jose. I do so love traveling.

Tuesday, March 08, 2005

Guidance toward the-one-true-path.

Paul Colton wrote an interesting article for Byte about XAML. Once sentence in the article was a real attention grabber:
XAML has many strengths, and Microsoft's ability to educate the marketplace and guide the .NET developer community may ultimately tip the balance to XAML.

Wow. I'm *SO* glad Microsoft is willing and able to educate and guide me. ;->

Monday, March 07, 2005

Thoughts Meandering From Interview Questions to Semantic Compilers.

I used to do a lot of interviews for programming positions. One of my favorite interview questions is:

What's the best book about programming you've ever read? What book should every programmer read?

(I know, that's two questions --- but hey, it's my interview (and my blog) so I make up the rules.)

There is no "right" aswer to this question, but there are a couple of wrong ones.

The worst answer is "I haven't read any programming books recently." The applicant can recover from this if she goes on to explain that she reads programming blogs, and magazines instead because the informaion is more current, but barring such a last-minute save, a negative answer is an interview stopper.

The next worst answer is a textbook required by a college course. If the interviewee is more than a couple of months out of college and hasn't learned anything since, well....

Responding with something like "How to write [fill-in-the specific-application] programs in [fill in the language] on [fill in the platform]" earns a barely passing grade. The interviewee is on the bubble and better come up with a reason why I should keep talking to them really quickly!

Oh yeah, and any book containing "for dummies" in the title is instant death <chuckle/>.

So, how would I respond to the question?

I might mention some recent read (ala "The Pragmatic Programmer.") or I might fall back on one of the very few books that have a profound impact on the way I program.

Looking back, there have been a lot of programming books, but very few that were life-changing. "Elements of Programming Style" counts (yes, that was a long time ago, but it's still worth reading.) Most programmers know about "Elements.." and it is actually used as a textbook in some college courses (which contradicts the "second-worst" judging criterion above. hmmm.)

Another book that had a major impact was "Writing Solid Code" by Steve Maguire. I'm not sure whether this was that good a book, or whether it just happened to be the right book at the right time for me. I should probably re-read it if I could find my copy.

One concept I acquired from "Writing Solid Code" is the idea of a compiler that checks semantics rather than (or in addition to) syntax. Wouldn't it be great if the compiler would tell you: "Yes, that's legal C++, but it's really not what you should have written." Actually, over time, compilers have gotten better at producing this type of warning messages. By flaging unused variables, statements with no effect, and such atrocities as:

if (a = b)

modern compilers bring your attention to possible semantic problems. This is a good thing.

But wouldn't it be nice if compilers could go further? If they could warn not only about obvious nonsense, but also questionable practices, bad algorithms, etc. we could write better programs faster.

One problem of course, is my "really cool technique" is your semantic abomination, and vice versa. In recent discussions here at work we haven't been able to agree on where the & should go in:

int & a;

Some say goes with a because you can say "int &a, b, *c;" to which I say -- not in MY semantic compiler you can't! Some say it goes with the int because it's "part of the type" to which I say, but how about const and static. Aren't they part of the type.

In case you haven't noticed, I think that & and * are first-class citizens and deserve to stand on their own rather than being piggybacked on another token -- but that's just my opinion and it is certainly subject to debate. It's also completely beside the point of this discussion.

Lack of agreement about what constitutes a good program makes it very difficult -- nay impossible -- to come up with a one-size-fits-all semantic compiler. So how about a compiler that "learns" your programming style, and flags departures? If you always indent by two, but happen to indent by four in one place -- well maybe that indicates the code is wrong. Better yet, if you always use braces (as you should (chuckle)) then an if statement with no braces should be flagged as an "error."

Hmmm. How well does this play on a team programming project?

Of course these are all "small scale" issues. The real benefit comes when the compiler can detect, for example, that you're doing a linear search of a (large enough) sorted list and suggest that a binary search would be a good idea here, or can look at an object full of get and set methods and advise you that you've really blown the encapsulization and should be writing domain-meaningful methods instead.

STL does makes some interesting strides in that area by declining to implement "unwise" methods on some containers. Java was also a step in this direction -- unfortunately a lot of the decisions that went into Java were one man's opinion so some valuable techinques were "tossed out with the bathwater." and some warts like uninitialized references made it into the language.

There's much more to be said on this topic, but this entry is getting too long. To be revisited...

Friday, March 04, 2005

The kind of person that keeps a parrot.

Tina was looking at my blog and pointed out that not everyone was familar with the Mark Twain quote. Although anyone who reads parrot mailing lists surely knows it, for the rest of you, click here.

Pet Peeve n+1: Thinking outside the box

The phrase "Think outside the box" ranks right up there with "Have a nice day :-)." and just barely below fingernails scraping on blackboard.

Most people use the phrase to mean "ignore the rules."

Trucker #1: Why are you stopping?
Trucker #2: Look at the sign on that overpass.
Trucker #1: Yeah, it says "Clearance 11 ft. 6 in." So what?
Trucker #2: The trailer we're pulling is 12 feet tall.
Trucker#1: (looks around) I don't see any cops. Let's go for it.

See. Trucker#1 is thinking outside the box the way most people use the phrase.

The origin of the phrase is a classic logic puzzle. Given nine points arranged in a 3x3 grid:

X X X
X X X
X X X

Draw a continuous series of four line segments that passes through each point exactly once.

The solution (which you know, right? (if not, there's a hint below)) involves extending the lines beyond the "borders" of the array. Hence, "Thinking outside the box."

You don't solve this puzzle by thinking "outside" the box. You solve it by realizing that there is no box outside of which to think! [Look again, do you see any box?]

Understanding the true constraints on a problem and finding creative solutions within those constraints -- good. Ignoring the constraints that happen to be inconvenient -- bad.


All of which applies to programming, too!

Hint:


X X X x
X X X
X X X
x

Wednesday, March 02, 2005

The Sins of Intel

Another entry in my Hack series. This one's a threefer:

A lot of people complain about the Intel architecture. It must have been really easy for hardware designers to build systems around the early Intel chips, 'cause you'd never find a software developer praising their design. Early Intel chips (and some not-so-early chips) were much harder to program than they needed to be.

Sin #1: A + (-B) != A - B
One of my favorite Intel sins, is that the engineer(s) who designed the early chips did not understand binary arithmetic! The way the chip was designed 5 + (-1) [that's five plus negative 1] did not produce the same answer as 5 - 1 [five minus one]! That one deserves an extra exclamation mark!

To explain:
On a four bit machine 5 + (-1) looks like:

0101
1111
1 0100

That 1 hanging out there is a carry bit. It is normal for a subtraction to generate a carry (it means you did NOT have to borrow!)

Unfortunately on Intel the flag that ends up holding that 1 is called the carry/borrow flag. If you add, it holds the carry. If you subtract it holds the borrow.

That means 5 + (-1) is four with a carry, whereas 5 - 1 is four without a borrow, but borrow and carry are stored in the same flag, so to do-the-right-thing with the carry bit you have to know how you got here.

The result is a lot of really sweet binary arithmetic techniques were just a little bit harder and messier (and hence a little bit less sweet) on the Intel machines.

Sin #2: Segment granularity

When Intel discovered they needed to go beyond the 16 bit address space of their early processors they added segment registers. This was a good move because it preserved compatibility with older software -- or at least made it relatively easy to migrate to the new processors.

The sin comes when you considered how the segment registers were factored into the address calculations. The segment registers have 16 bits -- making them easy to manipulate on a machine that's built around a 16 bit architecture, but in order to achieve the goal of extending the address space, the segment registers have to address units of memory larger than a single byte. Intel choose a 16 byte addressable chunk for the segment registers. That means the segment register is shifted four bits to the left when it takes part in the addressing calculations. The result is the 20 bit (one megabyte) address space that hampered the Intel processors for years! (Of course IBM and Microsoft managed to hack that into the 640K limit, but that's a different transgression.)

Suppose Intel had shifted the segment register by 8 bits rather than 4 bits. Downside is a granularity of 256 bytes rather than 16 bytes (big deal--not)

Upsides:

First of all, calculating the values to go into segment registers would have been much easier because it's a lot easier to move things by 8 bits than by four bits on an Intel chip, but thats only a minor advantage compared to the real plus which is that the address space just became 24 bits (16 megabytes rather than 1 megabyte) Admittedly 16 megabytes seems small today, but it took almost 10 years before we achieved that state. Countless man-centuries (yes and woman-centuries) were poured down the bottomless pit of extended memory and expanded memory hacks trying to compensate for Intel's short sightness.

Sin #3: The 80286
Ok so they forgot to figure out how to get from user mode back to kernel mode (D'oh) This inspired the truly byzantine technique of booting the whole damn machine to do a context switch back to the OS. Kool, eh!

Monday, February 28, 2005

Stealth wins

I finished weaving the "stealth scarf" (see previous dissussion) this weekend and just as I suspected the pattern is pretty much invisible. If you hold it at just the right angle in just the right light you can see it, but that's not what I was hoping for.

For my next bright idea.....