Boot Options: numproc

Syntax

To have the BCDEDIT tool set the numproc option for the currently running operating system, run the command

bcdedit /set numproc number

where number is any decimal integer.

To set the option at the Edit Boot Options Menu, add

/numproc=number

which is also the syntax for the option as a BOOT.INI switch in earlier Windows versions.

Behaviour

Microsoft’s documentation is unclear whether the numproc option counts logical or physical processors. In its form as a BOOT.INI switch for earlier versions of Windows, this option was barely documented:

This switch sets the number of processors that Windows will run at startup. With this switch, you can force a multiprocessor system to use only the quantity of processors (number) that you specify. This switch can help you troubleshoot performance problems and defective CPUs.

As a Boot Configuration Data (BCD) option, there seems to be no formal documentation, but the command bcdedit /? types osloader says of the option

Uses only the specified number of processors.

and the System Configuration applet among the Administrative Tools exposes the option as “Number of processors” in the dialog box that is reached from the Advanced Options button on the Boot tab. All these cases avoid the question of whether the number is of logical processors or physical.

The answer is: both.

This answer is of course ridiculous, but the fact is that the kernel at one time compares number with a count of physical processors and at later times with a count that may include logical processors. This different interpretation at different times is of itself a logic failure, but this is no mere coding error during programming. While the legalese of the license terms and the documentation of the numproc option both skirt around the logical-or-physical question, the presence of a coding error here is an unsurprising, and even inevitable, consequence of managerial inattention.

Technical Details

When the kernel interprets the /NUMPROC switch during initialisation and finds a non-zero number, it compares number with the count of registered processors that has just been read from the Kernel-RegisteredProcessors license value. If number is less, it becomes the new count of registered processors. The license value, whether altered by the /NUMPROC argument or not, is saved in an internal kernel variable (whose name KeRegisteredProcessors is known from Microsoft’s symbol files for the kernel).

To begin with then, numproc is counted in whatever type of processor counts for the license value. This is the first comparison referred to above, since the count of licensed processors is essentially a count of physical processors.

Already however, there is an effect that the admittedly terse documentation does not even hint at. If the numproc value is non-zero but is less than the license value, then numproc does not set “the number of processors that Windows will run at startup” as claimed by the documentation. What numproc actually sets is the number of processors that Windows will believe the licensee is entitled to run.

To point out this difference may look like splitting hairs. If the kernel will anyway ignore unlicensed processors, then what better way is there to remove some unwanted processors from use than to treat them as unlicensed? This coding, which dates at least back to Windows 2000, may even have been thought efficient, and with good reason at the time. Unfortunately, someone has subsequently coded for the kernel to remember its discovery of an unlicensed processor and to apply a penalty. So now the difference is very real. If you have two processors of the sort that counts for the license value, and you set numproc to 1, then the second of your processors is not just unused, it is treated as if unlicensed and you lose large-page support (which is your undocumented punishment for having an unlicensed processor).

Look now at how the kernel enumerates logical processors. Each newly enumerated logical processor may be in the same physical package as one that has already been enumerated, or not. When it is not, it must be the first logical processor for a new physical processor and it is counted against the license value. If the number of accepted processors (represented by the documented, exported variable KeNumberProcessors) has already reached the license value (represented by the internal variable KeRegisteredProcessors), then this newly enumerated physical processor is unlicensed and cannot be accepted for use. The kernel continues enumerating, but only in the hope of discovering additional logical processors for physical processors that have been accepted as licensed.

If a newly enumerated logical processor is in the same physical package as one that has already been enumerated, then in the typical case with no /NUMPROC switch the kernel simply increments the KeRegisteredProcessors variable and accepts the processor (thus also incrementing KeNumberProcessors). Of itself, this is a fine way to implement that additional logical processors of a licensed physical processor are automatically licensed. However, this code has as a side-effect that it makes a hybrid of the KeRegisteredProcessors variable, which now counts both logical and physical processors.

The inherent danger in what might otherwise be an abstract failure of logic is made real by a small variation that executes only if given a non-zero /NUMPROC argument. If a newly enumerated logical processor is in the same physical package as one that has already been enumerated, it is accepted as automatically licensed only if the current value of KeRegisteredProcessors. is less than the /NUMPROC argument. This is the second comparison referred to above. It is a nonsensical comparison of physical processors against a mixture of logical and physical. If KeRegisteredProcessors is already as great as the /NUMPROC argument, then as far as concerns licensing, this logical processor is treated essentially as a new physical processor—and, worse, as an unlicensed physical processor.

Demonstration

It may help to have a relatively easy demonstration that something really is wrong.

First, write a program that tests for large-page support, e.g., by reporting the return value of the documented GetLargePageMinimum function. Second, on a machine with one dual-core processor, i.e., with one physical processor containing two logical processors, start Windows normally, then run the program to confirm that large pages are ordinarily supported. Third, restart Windows but press F10 to get the Edit Boot Options menu. Add /NUMPROC=1 to the active options, then continue. Run the program and see that large pages are not supported.

The license count for processors has been set to 1 (by code described above as the first comparison). The coding error (described above as the second comparison) has confused whether the license count is of physical or logical processors. The second logical processor has been treated as a second physical processor and deemed unlicensed.

Workaround

To run just the first logical processor on a machine with one dual-core processor, there is an alternative. Use the BCD option onecpu or its equivalent /onecpu switch in the Edit Boot Options menu. This is interpreted by the HAL to mean that only one logical processor should be disclosed. The kernel never learns that a second logical or physical processor is available. That’s not exactly the same as might be expected from setting numproc to 1, but it’s as close as you’ll get until Microsoft acknowledges the problem and fixes it.