September, 2004

  • The Old New Thing

    How to host an IContextMenu, part 2 - Displaying the context menu


    Instead of invoking a fixed verb, we'll ask the user to choose from the context menu and invoke the result.

    Make these changes to the OnContextMenu function:

    #define SCRATCH_QCM_FIRST 1
    #define SCRATCH_QCM_LAST  0x7FFF
    #define HANDLE_WM_CONTEXTMENU(hwnd, wParam, lParam, fn) \
        ((fn)((hwnd), (HWND)(wParam), GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)), 0L)
    // WARNING! Incomplete and buggy! See discussion
    void OnContextMenu(HWND hwnd, HWND hwndContext, int xPos, int yPos)
      POINT pt = { xPos, yPos };
      if (pt.x == -1 && pt.y == -1) {
        pt.x = pt.y = 0;
        ClientToScreen(hwnd, &pt);
      IContextMenu *pcm;
      if (SUCCEEDED(GetUIObjectOfFile(hwnd, L"C:\\Windows\\clock.avi",
                       IID_IContextMenu, (void**)&pcm))) {
        HMENU hmenu = CreatePopupMenu();
        if (hmenu) {
          if (SUCCEEDED(pcm->QueryContextMenu(hmenu, 0,
                                 SCRATCH_QCM_FIRST, SCRATCH_QCM_LAST,
                                 CMF_NORMAL))) {
            int iCmd = TrackPopupMenuEx(hmenu, TPM_RETURNCMD,
                                        pt.x, pt.y, hwnd, NULL);
            if (iCmd > 0) {
              CMINVOKECOMMANDINFOEX info = { 0 };
              info.cbSize = sizeof(info);
              info.fMask = CMIC_MASK_UNICODE;
              info.hwnd = hwnd;
              info.lpVerb  = MAKEINTRESOURCEA(iCmd - SCRATCH_QCM_FIRST);
              info.lpVerbW = MAKEINTRESOURCEW(iCmd - SCRATCH_QCM_FIRST);
              info.nShow = SW_SHOWNORMAL;

    The first change addresses the first issue brought up in the discussion of the WM_CONTEXTMENU message and fixes the HANDLE_WM_CONTEXTMENU message.

    The second change addresses the second issue, and that's the special handling of keyboard-invoked context menus. When we receive a keyboard-invoked context menu, we move it to the (0, 0) position of our client area. This keeps the context menu displayed in a vaguely sane position. (If we were a container with objects, it would have been better to display the context menu over the selected sub-object.)

    The third change actually does what we're talking about: Displaying the context menu to the user, collecting the result, and acting on it.

    You are certainly familiar with the TrackPopupMenuEx function. Here we use the TPS_RETURNCMD flag to indicate that the item the user selected should be returned by the function instead of being posted as a WM_COMMAND to our window.

    This highlights the importance of the fact that SCRATCH_QCM_FIRST is 1 and not zero. If it were zero, then we wouldn't be able to distinguish between the user selecting item zero and the user cancelling the menu.

    Once we are confident that the user has selected an item from the menu, we fill out a CMINVOKECOMMANDEX structure, specifying the user's selection in the two verb fields and indicating the invocation point via the ptInvoke member.

    Note that when you invoke a command by menu ID, you must specify the offset of the menu item relative to the starting point passed to IContextMenu::QueryContextMenu. That's why we subtracted SCRATCH_QCM_FIRST.

    When you run this program, you may notice that some things don't quite work. Most obviously, the Open With and Send To submenus don't work, but there are more subtle bugs too. We'll address them over the next few days.

  • The Old New Thing

    Swedes struggle with the meaning of sick leave


    As part of the continuing campaign to shed their hard-working stereotype, perhaps taking a cue from their more well-adjusted Norwegian neighbors, Swedes have been taking dubious sick leave in record numbers.

    [A] study showed 40 percent believe it is enough to feel tired to stay home and draw benefits.

    A survey of 1,002 Swedes by the board also showed 65 percent believed they could go on sick leave if they felt stressed at work and 41 percent thought a conflict with their boss or workmates was a good enough reason.

    One fifth thought a strike at the child care center also made them eligible for the benefits and 71 percent said family problems entitled them always or sometimes to sick leave.

    Reuters claims Dagens Nyheter as the source, but if you punch "Anna Hedborg" into DN:s search engine, asking for articles within the last week, it finds nothing. But if you ask Google, the article is right there: Trötta sjukskriver sig (The tired call in sick). Once again, Google finds a web page that the site itself cannot.

    On a somewhat related note, a different type of sick-leave abuse has been detected.

    According to the Social Insurance Board, several people have been drawing sick-leave pay for several years, while simultaneously working another job and receiving unemployment benefits.

    Some cases are of ordinary people who defraud the Board in small amounts but over a long period of time. In the other cases, there are links to serious economic crime where, for example, money was paid out to fictitious employees at nonexistent companies.

    (Raymond's bad translation.)

    [Typo fixed 25 September.]

  • The Old New Thing

    Pitfalls in handling the WM_CONTEXTMENU message


    Before we continue with our IContextMenu discussion, I need to take a little side trip and discuss the subtleties of the WM_CONTEXTMENU message.

    First, a correction to the existing <windowsx.h> header file:

    #define HANDLE_WM_CONTEXTMENU(hwnd, wParam, lParam, fn) \
        ((fn)((hwnd), (HWND)(wParam), GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)), 0L)

    Apparently, HANDLE_WM_CONTEXTMENU was overlooked when the <windowsx.h> header file gained multimonitor support.

    The second subtlety of the WM_CONTEXTMENU message is the recognition that context menus can be invoked from the keyboard, not just by the mouse. If you have a 104-key keyboard, you will probably have a menu key to the right of your space bar. (Laptop owners: You're on your own. Laptop keyboards are hardly standardized.) Alternatively, you can type Shift+F10 to get the same effect.

    When the user invokes a context menu from the keyboard, the x and y coordinates are both -1. In this case, you should display the context menu for the currently-selected item (or items, if a multiple selection is active). If you miss this detail, then you will end up hit-testing against (-1, -1) and probably not find anything.

    Okay, now that these remarks on the WM_CONTEXTMENU message are out of the way, we can return to our discussion of the IContextMenu interface next time.

  • The Old New Thing

    How to host an IContextMenu, part 1 - Initial foray


    Most documentation describes how to plug into the shell context menu structure and be a context menu provider. If you read the documentation from the other side, then you also see how to host the context menu. (This is the first of an eleven-part series with three digressions. Yes, eleven parts—sorry for all you folks who are in it just for the history articles. I'll try to toss in an occasional amusing diversion.)

    The usage pattern for an IContextMenu is as follows:

    The details of this are explained in Creating Context MenuHandlers from the point of view of the IContextMenu implementor.

    The Shell first calls IContextMenu::QueryContextMenu. It passes in an HMENU handle that the method can use to add items to the context menu. If the user selects one of the commands, IContextMenu::GetCommandString is called to retrieve the Help string that will be displayed on the Microsoft Windows Explorer status bar. If the user clicks one of the handler's items, the Shell calls IContextMenu::InvokeCommand. The handler can then execute the appropriate command.

    Read it from the other side to see what it says you need to do as the IContextMenu host:

    The IContextMenu host first calls IContextMenu::QueryContextMenu. It passes in an HMENU handle that the method can use to add items to the context menu. If the user selects one of the commands, IContextMenu::GetCommandString is called to retrieve the Help string that will be displayed on the host's status bar. If the user clicks one of the handler's items, the IContextMenu host calls IContextMenu::InvokeCommand. The handler can then execute the appropriate command.

    Exploring the consequences of this new interpretation of the context menu documentation will be our focus for the next few weeks.

    Okay, let's get started. We begin, as always, with our scratch program. I'm going to assume you're already familiar with the shell namespace and pidls so I can focus on the context menu part of the issue.

    #include <shlobj.h>
    HRESULT GetUIObjectOfFile(HWND hwnd, LPCWSTR pszPath, REFIID riid, void **ppv)
      *ppv = NULL;
      HRESULT hr;
      LPITEMIDLIST pidl;
      SFGAOF sfgao;
      if (SUCCEEDED(hr = SHParseDisplayName(pszPath, NULL, &pidl, 0, &sfgao))) {
        IShellFolder *psf;
        LPCITEMIDLIST pidlChild;
        if (SUCCEEDED(hr = SHBindToParent(pidl, IID_IShellFolder,
                                          (void**)&psf, &pidlChild))) {
          hr = psf->GetUIObjectOf(hwnd, 1, &pidlChild, riid, NULL, ppv);
      return hr;

    This simple function takes a path and gets a shell UI object from it. We convert the path to a pidl with SHParseDisplayName, then bind to the pidl's parent with SHBindToParent, then ask the parent for the UI object of the child with IShellFolder::GetUIObjectOf. I'm assuming you've had enough experience with the namespace that this is ho-hum.

    (The helper functions SHParseDisplayName and SHBindToParent don't do anything you couldn't have done yourself. They just save you some typing. Once you start using the shell namespace for any nontrivial amount of time, you build up a library of little functions like these.)

    For our first pass, all we're going to do is invoke the "Play" verb on the file when the user right-clicks. (Why right-click? Because a future version of this program will display a context menu.)

    #define SCRATCH_QCM_FIRST 1
    #define SCRATCH_QCM_LAST  0x7FFF
    void OnContextMenu(HWND hwnd, HWND hwndContext, UINT xPos, UINT yPos)
      IContextMenu *pcm;
      if (SUCCEEDED(GetUIObjectOfFile(hwnd, L"C:\\Windows\\clock.avi",
                       IID_IContextMenu, (void**)&pcm))) {
        HMENU hmenu = CreatePopupMenu();
        if (hmenu) {
          if (SUCCEEDED(pcm->QueryContextMenu(hmenu, 0,
                                 SCRATCH_QCM_FIRST, SCRATCH_QCM_LAST,
                                 CMF_NORMAL))) {
            CMINVOKECOMMANDINFO info = { 0 };
            info.cbSize = sizeof(info);
            info.hwnd = hwnd;
            info.lpVerb = "play";
        HANDLE_MSG(hwnd, WM_CONTEXTMENU, OnContextMenu);

    As noted in the checklist above, first we create the IContextMenu, then initialize it by calling IContextMenu::QueryContextMenu. Notice that even though we don't intend to display the menu, we still have to create a popup menu because IContextMenu::QueryContextMenu requires on. We don't actually display the resulting menu, however; instead of asking the user to pick an item from the menu, we make the choice for the user and select "Play", filling in the CMINVOKECOMMANDINFO structure and invoking it.

    But how did we know that the correct verb was "Play"? In this case, we knew because we hard-coded the file to "clock.avi" and we knew that AVI files have a "Play" verb. But of course that doesn't work in general. Before getting to invoking the default verb, let's first take the easier step of asking the user what verb to invoke. That exercise will actually distract us for a while, but we'll come back to the issue of the default verb afterwards.

    If the code above is all you really wanted (invoking a fixed verb on a file), then you didn't need to go through all the context menu stuff. The code above is equivalent to calling the ShellExecuteEx function, passing the SEE_MASK_INVOKEIDLIST flag to indicate that you want the invoke to go through the IContextMenu.

    [Typo fixed 25 September.]

  • The Old New Thing

    Why does my mouse/touchpad sometimes go berzerk?


    Each time you move a PS/2-style mouse, the mouse send three bytes to the computer. For the sake of illustration, let's say the three bytes are x, y, and buttons.

    The operating system sees this byte stream and groups them into threes:

    x y b x y b x y b x y b

    Now suppose the cable is a bit jiggled loose and one of the "y"s gets lost. The byte stream loses an entry, but the operating system doesn't know this has happened and keeps grouping them in threes.

    x y b x b x y b x y b x

    The operating system is now out of sync with the mouse and starts misinterpreting all the data. It receives a "y b x" from the mouse and treats the y byte as the x-delta, the b byte as the y-delta, and the x byte as the button state. Result: A mouse that goes crazy.

    Oh wait, then there are mice with wheels.

    When the operating system starts up, it tries to figure out whether the mouse has a wheel and convinces it to go into wheel mode. (You can influence this negotiation from Device Manager.) If both sides agree on wheeliness, then the mouse generates four bytes for each mouse motion, which therefore must be interpreted something like this:

    x y b w x y b w x y b w x y b w

    Now things get really interesting when you introduce laptops into the mix.

    Many laptop computers have a PS/2 mouse port into which you can plug a mouse on the fly. When this happens, the built-in pointing device is turned off and the PS/2 mouse is used instead. This happens entirely within the laptop's firmware. The operating system has no idea that this switcheroo has happened.

    Suppose that when you turned on your laptop, there was a wheel mouse connected to the PS/2 port. In this case, when the operating system tries to negotiate with the mouse, it sees a wheel and puts the mouse into "wheel mode", expecting (and fortunately receiving) four-byte packets.

    Now unplug your wheel mouse so that you revert to the touchpad, and let's say your touchpad doesn't have a wheel. The touchpad therefore spits out three-byte mouse packets when you use it. Uh-oh, now things are really messed up.

    The touchpad is sending out three-byte packets, but the operating system thinks it's talking to that mouse that was plugged in originally and continues to expect four-byte packets.

    You can imagine the mass mayhem that ensues.

    Moral of the story: If you're going to hot-plug a mouse into your laptop's PS/2 port, you have a few choices.

    • Always use a nonwheel mouse, so that you can plug and unplug with impunity, since the nonwheel mouse and the touchpad both use three-byte packets.
    • If you turn on the laptop with no external mouse, then you can go ahead and plug in either a wheeled or wheel-less mouse. Plugging in a wheel-less mouse is safe because it generates three-byte packets just like the touchpad. And plugging in a wheeled mouse is safe because the wheeled mouse was not around for the initial negotiation, so it operates in compatibility mode (i.e., it pretends to be a wheel-less mouse). In this case, the mouse works, but you lose the wheel.
    • If you turn on the laptop with a wheel mouse plugged in, never unplug it because once you do, the touchpad will take over and send three-byte packets and things will go berzerk.

    Probably the easiest way out is to avoid the PS/2 mouse entirely and just use a USB mouse. This completely sidesteps the laptop's PS/2 switcheroo.

  • The Old New Thing

    What happens when you specify RegexOptions.ECMAScript?


    The RegexOptions.ECMAScript flag changes the behavior of .NET regular expressions. One of the changes I had discussed earlier was with respect to matching digits. For those who want to know more, a summary of the differences is documented in MSDN under the devious title "ECMAScript vs. Canonical Matching Behavior".

    Apparently some people had trouble finding that page, so I figured I'd point to it explicitly.

  • The Old New Thing

    A visual history of spam (and virus) email


    I have kept every single piece of spam and virus email since mid-1997. Occasionally, it comes in handy, for example, to add naïve Bayesian spam filter to my custom-written email filter. And occasionally I use it to build a chart of spam and virus email.

    The following chart plots every single piece of spam and virus email that arrived at my work email address since April 1997. Blue dots are spam and red dots are email viruses. The horizontal axis is time, and the vertical axis is size of mail (on a logarithmic scale). Darker dots represent more messages. (Messages larger than 1MB have been treated as if they were 1MB.)

    Note that this chart is not scientific. Only mail which makes it past the corporate spam and virus filters show up on the chart.

    Why does so much spam and virus mail get through the filters? Because corporate mail filters cannot take the risk of accidentally classifying valid business email as spam. Consequently, the filters have to make sure to remove something only if they has extremely high confidence that the message is unwanted.

    Okay, enough dawdling. Let's see the chart.

    Overall statistics and extrema:

    • First message in chart: April 22, 1997.
    • Last message in chart: September 10, 2004.
    • Smallest message: 372 bytes, received March 11, 1998.
      From: 15841.
      To: 15841.
      Subject: About your account...
      Content-Type: text/plain; charset=ISO-8859-1
      Content-Transfer-Encoding: 7bit
    • Largest message: 1,406,967 bytes, received January 8, 2004. HTML mail with a lot of text including 41 large images. A slightly smaller version was received the previous day. (I guess they figured that their first version wasn't big enough, so they sent out an updated version the next day.)
    • Single worst spam day by volume: January 8, 2004. That one monster message sealed the deal.
    • Single worst spam day by number of messages: August 22, 2002. 67 pieces of spam. The vertical blue line.
    • Single worst virus day: August 24, 2003. This is the winner both by volume (1.7MB) and by number (49). The red splotch.
    • Totals: 227.6MB of spam in roughly 19,000 messages. 61.8MB of viruses in roughly 3500 messages.

    Things you can see on the chart:

    • Spam went ballistic starting in 2002. You could see it growing in 2001, but 2002 was when it really took off.
    • Vertical blue lines are "bad spam days". Vertical red lines are "bad virus days".
    • Horizontal red lines let you watch the lifetime of a particular email virus. (This works only for viruses with a fixed-size payload. Viruses with variable-size payload are smeared vertically.)
    • The big red splotch in August 2003 around the 100K mark is the Sobig virus.
    • The horizontal line in 2004 that wanders around the 2K mark is the Netsky virus.
    • For most of this time, the company policy on spam filtering was not to filter it out at all, because all the filters they tried had too high a false-positive rate. (I.e., they were rejecting too many valid messages as spam.) You can see that in late 2003, the blue dot density diminished considerably. That's when mail administrators found a filter whose false-positive rate was low enough to be acceptable.

    As a comparison, here's the same chart based on email received at one of my inactive personal email addresses.

    This particular email address has been inactive since 1995; all the mail it gets is therefore from harvesting done prior to 1995. (That's why you don't see any red dots: None of my friends have this address in their address book since it is inactive.) The graph doesn't go back as far because I didn't start saving spam from this address until late 2000.

    Overall statistics and extrema:

    • First message in chart: September 2, 2000.
    • Last message in chart: September 10, 2004.
    • Smallest message: 256 bytes, received July 24, 2004.
      Received: from ([]) by ...
               Sat, 24 Jul 2004 12:30:35 -0700
      X-Message-Info: 10
    • Largest message: 3,661,900 bytes, received April 11, 2003. Mail with four large bitmap attachments, each of which is a Windows screenshot of Word with a document open, each bitmap showing a different page of the document. Perhaps one of the most inefficient ways of distributing a four-page document.
    • Single worst spam day by volume: April 11, 2003. Again, the monster message drowns out the competition.
    • Single worst spam day by number of messages: October 3, 2003. 74 pieces of spam.
    • Totals: 237MB of spam in roughly 35,000 messages.

    I cannot explain the mysterious "quiet period" at the beginning of 2004. Perhaps my ISP instituted a filter for a while? Perhaps I didn't log on often enough to pick up my spam and it expired on the server? I don't know.

    One theory is that the lull was due to uncertainty created by the CAN-SPAM Act, which took effect on January 1, 2004. I don't buy this theory since there was no significant corresponding lull at my other email account, and follow-up reports indicate that CAN-SPAM was widely disregarded. Even in its heyday, compliance was only 3%.

    Curiously, the trend in spam size for this particular account is that it has been going down since 2002. In the previous chart, you could see a clear upward trend since 1997. My theory is that since this second dataset is more focused on current trends, it missed out on the growth trend in the late 1990's and instead is seeing the shift in spam from text to <IMG> tags.

  • The Old New Thing

    Interlocked operations don't solve everything


    Interlocked operations are a high-performance way of updating DWORD-sized or pointer-sized values in an atomic manner. Note, however, that this doesn't mean that you can avoid the critical section.

    For example, suppose you have a critical section that protects a variable, and in some other part of the code, you want to update the variable atomically. "Well," you say, "this is a simple imcrement, so I can skip the critical section and just do a direct InterlockedIncrement. Woo-hoo, I avoided the critical section bottleneck."

    Well, except that the purpose of that critical section was to ensure that nobody changed the value of the variable while the protected section of code was running. You just ran in and changed the value behind that code's back.

    Conversely, some people suggested emulating complex interlocked operations by having a critical section whose job it was to protect the variable. For example, you might have an InterlockedMultiply that goes like this:

    // Wrong!
    LONG InterlockedMultiply(volatile LONG *plMultiplicand, LONG lMultiplier)
      LONG lResult = *plMultiplicand *= lMultiplier;
      return lResult;

    While this code does protect against two threads performing an InterlockedMultiply against the same variable simultaneously, it fails to protect against other code performing a simple atomic write to the variable. Consider the following:

    int x = 2;
      InterlockedMultiply(&x, 5);

    If the InterlockedMultiply were truly interlocked, the only valid results would be x=15 (if the interlocked increment beat the interlocked multiply) or x=11 (if the interlocked multiply beat the interlocked increment). But since it isn't truly interlocked, you can get other weird values:

    Thread 1Thread 2
    x = 2 at start
    InterlockedMultiply(&x, 5)
    load x (loads 2)
    x is now 3
    multiply by 5 (result: 10)
    store x (stores 10)
    x = 10 at end

    Oh no, our interlocked multiply isn't very interlocked after all! How can we fix it?

    If the operation you want to perform is a function solely of the starting numerical value and the other function parameters (with no dependencies on any other memory locations), you can write your own interlocked-style operation with the help of InterlockedCompareExchange.

    LONG InterlockedMultiply(volatile LONG *plMultiplicand, LONG lMultiplier)
      LONG lOriginal, lResult;
      do {
        lOriginal = *plMultiplicand;
        lResult = lOriginal * lMultiplier;
      } while (InterlockedCompareExchange(plMultiplicand,
                                          lResult, lOriginal) != lOriginal);
      return lResult;

    [Typo in algorithm fixed 9:00am.]

    To perform a complicated function on the multiplicand, we perform three steps.

    First, capture the value from memory: lOriginal = *plMultiplicand;

    Second, compute the desired result from the captured value: lResult = lOriginal * lMultiplier;

    Third, store the result provided the value in memory has not changed: InterlockedCompareExchange(plMultiplicand, lResult, lOriginal)

    If the value did change, then this means that the interlocked operation was unsucessful because somebody else changed the value while we were busy doing our computation. In that case, loop back and try again.

    If you walk through the scenario above with this new InterlockedMultiply function, you will see that after the interloping InterlockedIncrement, the loop will detect that the value of "x" has changed and restart. Since the final update of "x" is performed by an InterlockedCompareExchange operation, the result of the computation is trusted only if "x" did not change value.

    Note that this technique works only if the operation being performed is a pure function of the memory value and the function parameters. If you have to access other memory as part of the computation, then this technique will not work! That's because those other memory locations might have changed during the computation and you would have no way of knowing, since InterlockedCompareExchange checks only the memory value being updated.

    Failure to heed the above note results in problems such as the so-called "ABA Problem". I'll leave you to google on that term and read about it. Fortunately, everybody who talks about it also talks about how to solve the ABA Problem, so I'll leave you to read that, too.

    Once you've read about the ABA Problem and its solution, you should be aware that the solution has already been implemented for you, via the Interlocked SList functions.

  • The Old New Thing

    The x86 architecture is the weirdo


    The x86 architecture does things that almost no other modern architecture does, but due to its overwhelming popularity, people think that the x86 way is the normal way and that everybody else is weird.

    Let's get one thing straight: The x86 architecture is the weirdo.

    The x86 has a small number (8) of general-purpose registers; the other modern processors have far more. (PPC, MIPS, and Alpha each have 32; ia64 has 128.)

    The x86 uses the stack to pass function parameters; the others use registers.

    The x86 forgives access to unaligned data, silently fixing up the misalignment. The others raise a misalignment exception, which can optionally be emulated by the supervisor at an amazingly huge performance penalty.

    The x86 has variable-sized instructions. The others use fixed-sized instructions. (PPC, MIPS, and Alpha each have fixed-sized 32-bit instructions; ia64 has fixed-sized 41-bit instructions. Yes, 41-bit instructions.)

    The x86 has a strict memory model, where external memory access matches the order in which memory accesses are issued by the code stream. The others have weak memory models, requiring explicit memory barriers to ensure that issues to the bus are made (and completed) in a specific order.

    The x86 supports atomic load-modify-store operations. None of the others do.

    The x86 passes function return addresses on the stack. The others use a link register.

    Bear this in mind when you write what you think is portable code. Like many things, the culture you grow up with is the one that feels "normal" to you, even if, in the grand scheme of things, it is one of the more bizarre ones out there.

  • The Old New Thing

    How does Windows exploit hyperthreading?


    It depends which version of Windows you're asking about.

    For Windows 95, Windows 98, and Windows Me, the answer is simple: Not at all. These are not multiprocessor operating systems.

    For Windows NT and Windows 2000, the answer is "It doesn't even know." These operating systems are not hyperthreading-aware because they were written before hyperthreading was invented. If you enable hyperthreading, then each of your CPUs looks like two separate CPUs to these operating systems. (And will get charged as two separate CPUs for licensing purposes.) Since the scheduler doesn't realize the connection between the virtual CPUs, it can end up doing a worse job than if you had never enabled hyperthreading to begin with.

    Consider a dual-hyperthreaded-processor machine. There are two physical processors A and B, each with two virtual hyperthreaded processors, call them A1, A2, B1, and B2.

    Suppose you have two CPU-intensive tasks. As far as the Windows NT and Windows 2000 schedulers are concerned, all four processors are equivalent, so it figure it doesn't matter which two it uses. And if you're unlucky, it'll pick A1 and A2, forcing one physical processor to shoulder two heavy loads (each of which will probably run at something between half-speed and three-quarter speed), leaving physical processor B idle; completely unaware that it could have done a better job by putting one on A1 and the other on B1.

    Windows XP and Windows Server 2003 are hyperthreading-aware. When faced with the above scenario, those schedulers will know that it is better to put one task on one of the A's and the other on one of the B's.

    Note that even with a hyperthreading-aware processor, you can concoct pathological scenarios where hyperthreading ends up a net loss. (For example, if you have four tasks, two of which rely heavily on L2 cache and two of which don't, you'd be better off putting each of the L2-intensive tasks on separate processors, since the L2 cache is shared by the two virtual processors. Putting them both on the same processor would result in a lot of L2-cache misses as the two tasks fight over L2 cache slots.)

    When you go to the expensive end of the scale (the Datacenter Servers, the Enterprise Servers), things get tricky again. I refer still-interested parties to the Windows Support for Hyper-Threading Technology white paper.

    Update 06/2007: The white paper appears to have moved.

Page 2 of 4 (31 items) 1234