November, 2008

  • The Old New Thing

    The Ballard Locks will be empty this week

    • 9 Comments

    The Hiram M. Chittenden Locks (more commonly known as the Ballard Locks) are a common attraction in Seattle. But if you pay a visit for the next week, you'll find that the large set of locks will be empty. And not just empty of boats. Empty of water.

    The chamber has been emptied of water for annual maintenance, and if you stop by, you can take pictures of a big hole in the ground.

    According to The Seattle Times, items found upon draining the chamber include cell phones, a two-way radio, a boot, beer cans, and prescription glasses. Nothing too exciting, I have to admit. Now, if they had found a bicycle...

  • The Old New Thing

    Just because a method is called Refresh doesn't mean that it refreshes what you want

    • 21 Comments

    Here's a question from a customer:

    I made some changes related to my shell extension [details omitted], but the changes don't show up in the Explorer window when I refresh it. Any suggestions on how to solve this problem?

    When we asked how they were refreshing the Explorer window, we were expecting something like pressing F5 or calling SHChangeNotify with SHCNE_UPDATEDIR, or maybe calling IShellView::Refresh or possibly even calling WebBrowser.Refresh from script. But we definitely didn't expect this response:

    I'm invoking the Process.Refresh() method from the System.Diagnostics namespace.

    Just because a method is called Refresh doesn't mean that it refreshes what you want. I think this is somebody who just saw a method name, perhaps inspired by IntelliSense and— Boom! You have it!—assumed it did what was needed without actually reading the documentation to check. But you don't even need to read the documentation to know that Process.Refresh has no chance of working.

    Since it's a method on the Process class, the method is applicable to all processes. But certainly there is no generic way to tell a process to refresh. This magical Refresh method would have to know how to refresh Explorer windows, Firefox web pages, iTunes mp3 tags... And what would it even mean to refresh, say, a Notepad window? Does that mean to throw away all changes and reload the original document?

    How do you know that there is no generic way to tell a process to refresh? Well, for one thing, a single process like Explorer can be viewing multiple objects that can be refreshed; which one are you refreshing? Second, when you write your own program, how do you implement refresh? Do you respond to some standard system Refresh message? Or do you just add a Refresh option to your program's main menu and give it some arbitrary command ID? If there's not even a standard way to refresh your program's window, then how can there be a standard way to refresh all program windows?

    In this specific case, the Process.Refresh method refreshes the Process object's internal cache of process properties. It doesn't actually do anything to the process itself. How could it?

    It's like thinking that the Matrix.Rotate method rotates the entries in a matrix.

    Epilogue

    Actually, I'm scared by this customer's question for another reason: The fact that they even mentioned Process.Refresh suggests to me that they wrote their shell extension in managed code, which we already know is strongly disrecommended.

  • The Old New Thing

    Welcome to the 2008 holiday shopping season

    • 4 Comments

    Yes, maybe you're one of those crazy people who camps out in front of a store so you can be the first person in line to get this year's hot toy, or so you can snag one of the doorbuster deals. But be judicious in your rush, because the police are out there too, and they're shopping for something else.

  • The Old New Thing

    Not my finest hour: Getting instructions on doing something I've already done

    • 24 Comments

    Last year, I sent some email to the people who run our team's check-in validation tool asking how I could add a new rule to the validation tests. One of the members wrote back, "You do it just like this guy," and sent me a reference to another check-in that added a validation rule.

    That other check-in was made by me.

    But wait, it gets better.

    That other check-in? It added the very rule I was thinking about adding.

  • The Old New Thing

    The cost-benefit analysis of bitfields for a collection of booleans

    • 68 Comments

    Consider a class with a bunch of BOOL members:

    // no nitpicking over BOOL vs bool allowed
    class Pear {
     ...
     BOOL m_peeled;
     BOOL m_sliced;
     BOOL m_pitted;
     BOOL m_rotten;
     ...
    };
    

    You might be tempted to convert the BOOL fields into bitfields:

    class Pear {
     ...
     BOOL m_peeled:1;
     BOOL m_sliced:1;
     BOOL m_pitted:1;
     BOOL m_rotten:1;
     ...
    };
    

    Since a BOOL is typedef'd as INT (which on Windows platforms is a signed 32-bit integer), this takes sixteen bytes and packs them into one. That's a 93% savings! Who could complain about that?

    How much did that savings cost you, and how much did you save anyway?

    Let's look at the cost of that savings. Code that updated the plain BOOL m_sliced member could do it by simply storing the result into the member. Since it was a normal field, this could be accomplished directly:

      mov [ebx+01Ch], eax ; m_sliced = sliced
    

    On the other hand, when it's a bitfield, updating it becomes trickier:

      add eax, eax        ; shift "sliced" into the correct position
      xor eax, [ebx+01Ch] ; merge "sliced" with other bits
      and eax, 2
      xor [ebx+01Ch], eax ; store the new bitfield
    

    Exercise: Figure out how the above trick works.

    Converting a BOOL to a single-bit field saved three bytes of data but cost you eight bytes of code when the member is assigned a non-constant value. Similarly, extracting the value gets more expensive. What used to be

     push [ebx+01Ch]      ; m_sliced
     call _Something@4    ; Something(m_sliced);
    

    becomes

     mov  ecx, [ebx+01Ch] ; load bitfield value
     shl  ecx, 30         ; put bit at top
     sar  ecx, 31         ; move down and sign extend
     push ecx
     call _Something@4    ; Something(m_sliced);
    

    The bitfield version is bigger by nine bytes.

    Let's sit down and do some arithmetic. Suppose each of these bitfielded fields is accessed six times in your code, three times for writing and three times for reading. The cost in code growth is approximately 100 bytes. It won't be exactly 102 bytes because the optimizer may be able to take advantage of values already in registers for some operations, and the additional instructions may have hidden costs in terms of reduced register flexibility. The actual difference may be more, it may be less, but for a back-of-the-envelope calculation let's call it 100. Meanwhile, the memory savings was 15 byte per class. Therefore, the breakeven point is seven. If your program creates fewer than seven instances of this class, then the code cost exceeds the data savings: Your memory optimization was a memory de-optimization.

    Even if you manage to come out ahead in the accounting ledger, it may be a win of just a few hundred bytes. That's an awful lot of extra hassle to save a few hundred bytes. All somebody has to do is add an icon to a dialog box and your savings will vanished.

    When I see people making these sorts of micro-optimizations, sometimes I'll ask them, "How many instances of this class does the program create?" and sometimes the response will be, "Oh, maybe a half dozen. Why do you ask?"

    But wait, there's more. Packing all these members into a bitfield has other costs. You lose the ability to set a hardware write breakpoint on a specific bit, since hardware breakpoints are done at the byte level (at a minimum). You also lose atomicity: An update to m_sliced will interfere with a simultaneous update to m_peeled on another thread, since the update process merges the two values and stores the result non-atomically. (Note that you also lose atomicity if you had used a byte-sized bool instead of a 32-bit BOOL because some CPU architectures such as the original Alpha AXP cannot access memory in units smaller than a DWORD.)

    These are just a few things to take into account when considering whether you should change your fields to bitfields. Sure, bitfields save data memory, but you have to balance it against the cost in code size, debuggability, and reduced multithreading. If your class is going to be instantiated only a few times (and by "a few" I'm thinking less than a few thousand times), then these costs most likely exceed the savings.

  • The Old New Thing

    Yes, I filed an expense report for a hair dryer, why do you ask?

    • 25 Comments

    Back in the late 1990's one of my colleagues (who is now in Office Labs—check it out, they've got some pretty cool stuff) filed an expense report for a hair dryer, and it was accepted. But what valid business purpose would there be for a tester to buy a hair dryer?

    At the time, my colleague worked as a tester for Windows power management. One of the things that needed to be tested was whether the motherboard accurately reported thermal stress (translation: overheating) to the operating system and whether the operating system responded appropriately to these reports. And when the project started, the most convenient way to get a motherboard to overheat on cue was to blast it with a hair dryer.

    Of course, more official equipment arrived later, but it was definitely a cruel (or clever, depending on your point of view) way to introduce new people to the team: "This is your desk, and here is your hair dryer."

    Bonus makeshift testing hardware: During approximately the same era, a joystick tester hooked up the joystick to a series of mechanical linkages, all driven by a record player turntable, in order to stress test the joystick for hours on end. And no, I don't know whether it ran at 33 rpm or 45.

  • The Old New Thing

    When you're walking around a city, you usually forget to look up

    • 7 Comments

    When you're walking around a city, you usually forget to look up. It takes a landmark building to prompt you to admire anything above the ground floor.

    For those in the Seattle area, the Seattle Times included a brief walking tour of Seattle architecture. The article doesn't mention one of my favorites: The roll-on deodorant building on the corner of Seneca and Second. The effect is most striking if you if you stand on Second and Union and look southeast at night. (Sadly, under the dome is just a roomful of machines.)

  • The Old New Thing

    Consequences of the Explorer view model: If you create a view, then you succeeded, even if you'd rather fail

    • 60 Comments

    Commenter Anonymous asked why navigating to a drive with no media displays a dialog instead of showing the error message in the view.

    This is an unfortunate consequence of Explorer's browser/view model. The shell browser binds to the IShellFolder and asks for the view by calling IShellFolder::CreateViewWindow. The view window calls IShellFolder::EnumObjects to figure out what to show in the view—and here is where the error dialog appears asking you to insert a disc into the drive.

    The problem is that IShellFolder::EnumObjects has to return an enumerator or an error code. There is no return value that says "Um, yeah, could you display this text instead?" In a narrow sense, there's no way to return it since there is no way to return a string from IShellFolder::EnumObjects, but it's also not possible in a broader sense, since there is no rule that says only shell views can call IShellFolder::EnumObjects. Anybody can bind to a shell folder and enumerate its contents. And most of them don't have any place to display a text message instead of the enumerated objects. For example, the folder tree uses IShellFolder::EnumObjects to fill in children of a node. If you expand a node for an empty floppy drive, where is the "Sorry" message supposed to appear?

    Now, you might say, "Well, make a special case for Explorer," and maybe that's the right thing to do, but designing in a special case to a general interface just for one program tends to create resentment for others: "How come Explorer can do this but my program can't?"

  • The Old New Thing

    Rachmaninov had big hands: An illustration

    • 19 Comments

    Rachmaninov's Prelude in C# minor, Op. 3, No. 2, performed as it is written, by classical music comedy duo Igudesman & Joo.

    I tried to learn that piece once. I didn't last long.

  • The Old New Thing

    The great thing about priorities is that you can always go one higher

    • 42 Comments

    The phenomenon I call priority inflation has spread to product planning documents as well. Back in the old days, there were three priority levels:

    • Priority 1: must have. If you don't accomplish a priority 1 item, you may as well just cancel the project because it ain't shipping.
    • Priority 2: should have. If you don't accomplish a priority 2 item, the product is significantly weaker, but you can still ship it.
    • Priority 3: nice to have. If you don't accomplish a priority 3 item, it's not quite as awesome as it could have been, but it's still a good product.

    Over the past few years, I've seen a shift in the labelling of priorities in planning documents. A new priority has been introduced: Priority Zero. Nobody has explained to me what Priority 0 means, but I assume somebody invented it to emphasize that the feature is even more critical than priority 1. Mind you, I'm not sure what could be more important to a project than "If we don't do this, we're all fired." Maybe "If we don't do this, the earth will explode."

    As you might expect, priority inflation has a trickle-down effect. People whose features had been assigned priority 1 said, "Hey, how come my feature isn't priority 0? It's just as critical as that other guy's feature." Soon, everything that was priority 1 got reclassified as priority 0. Nature abhors a vacuum, so all the priority 2 items got reclassified as priority 1, and the priority 3 items got reclassified as priority 2.

    In the end, nothing changed aside from the names on the buckets. It's been years since I've seen a planning document with any priority 3 items. It's all zero, one, and two now.

    Wait, I lied. The meaning of the last bucket (the former priority 3, now named priority 2) has changed. It used to be things that would be nice to have, but now it appears to be used for something other people suggested which I didn't think was important, but I didn't want to be mean and reject it outright, so I'm listing it here to make those people feel better and showing that their "voice was heard," but don't kid yourself; we're not going to do it. In other words, priority 2 means No.

    I give it three years before somebody decides that an issue is even more critical than priority 0 and labels it Priority −1.

    Epilogue: After I originally wrote this entry, I've learned that some teams have indeed come up with a priority level even more important than Priority 0. It's called Priority Now.

Page 1 of 4 (33 items) 1234