Advanced Function: Adjust CPU Affinity for a Process using PowerShell

Today I’m typing off the shell in Queenstown, New Zealand, it’s brisk out and the sun is shining. If you ever have the fortune of visiting New Zealand (aka Middle Earth), Queenstown is a must-see, must-do, must-go destination – coined the ‘adventure capital of the world’. But of course, as this isn’t a travel or adventurer’s blog, surrounded by snowy mountain ranges, I sit here wondering what the maiden post for Off the Shell will be!

I have settled on kicking off with a custom advanced function, one that you may or may not have a use for yourself. I see it as something that’s particularly useful to anyone working in a Server-oriented environment, specifically running application servers or the like, where resource management & performance may be of great importance – however, you may be surprised, my use-case was in an endpoint environment.


Set the Processor affinity of a Process

Firstly, what’s CPU Affinity? In this context, it is a property given to processes, that controls which CPU cores a process itself is bound to. The property is a masked value, it’s a sum unique to the combination of CPU cores you select. By default, a process will have 100% CPU affinity – it is bound to all CPU cores.

And what if we want to limit what CPU cores a certain process or group of processes can run against? What if we’re running pools of applications on a single server and want to isolate the workloads to different CPU cores in order to maintain the availability of services in the event another is maxing out the CPU load, or any other performance needs?

I recently created a routine background task that measured disk usage by known culprits within that environment of excessive disk usage. The task had to run on all of our Windows 8.1 devices, as a means to maintain disk availability due to limitations in capacity and incidents occurring with users running out of disk space. One of the mechanisms used in this task is CleanMgr.exe – quite the CPU hog when it gets into the swing of things. At first, I set this process to run at a lower CPU priority (other processes will take precedence) which in normal circumstances would be sufficient, however, after a few cycles it became obvious that it just wasn’t quite good enough.

I next decided that I would isolate the CPU cores that this process runs against and found there wasn’t an immediately simple way to do it – you have to calculate the mask value for the combination of CPU’s you choose to use. In comes Set-ProcessCPUAffinity. I created this function to simplify the process of selecting CPU’s and to run adaptively regardless of the number of CPU’s a computer has, just by naming a process, or by supplying its ID & specifying the cores to isolate the process to.

Alongside setting the priority of CleanMgr.exe to below normal, I’m now also able to simply direct it to use 2 of the CPU’s and all other processes can continue on as normal without perceivable user impact – and that’s the problem we were trying to solve. If the task was going to impact users’ ability to work in uncontrollable circumstances, then it was not much better than the original problem it was designed to solve (to preserve disk space).

Let’s jump right into the shell and see how Set-ProcessCPUAffinity works. First I need to dot-source it into my session.

Now I can get stuck in. Let’s start with Chrome.

What you can see here, is that a table of processes with the name ‘Chrome’ was returned to the console. This is because the function was written to be pipeline-friendly, and so both receives and outputs Process objects as part of the pipeline. Let’s demonstrate some more examples of what I mean and how this can be used.

Let’s get all of the Chrome processes that are running, and reset their affinity back to normal, then display the process name & affinity afterwards.

The ProcessorAffinity values you see will vary by the number of CPU’s you have, in my laptop here I have 8, and so 100% affinity comes to 255. Let’s do this again but select some cores to bind Chrome to.

Now you can see the affinity for the Chrome processes is 40, and 40 is the mask attributed to cores 4 and 6. The beauty in this is that I can also append Set-ProcessCPUAffinity to a Start-Process command as long as I pass it along the pipeline with -PassThru.

Core number 4 has an affinity mask of 8, and that’s now the CPU that the new PowerShell process I just started is bound to. And because Set-ProcessCPUAffinity outputs the processes we’re dealing with, I can use them in the pipeline too.

As you can see, I got a couple of errors. In this case, Wait-Process throws these errors because the other PowerShell processes I spun up are running interactively and haven’t closed before the timeout I set of 5 seconds, but this isn’t how Wait-Process would be used in this context – let’s demonstrate something more real world-like.

Wait-Process doesn’t give us anything interesting unless we add the -PassThru parameter, but what we did here was illustrate how Set-ProcessCPUAffinity could be used in conjunction with other workloads, in this case we started a PowerShell script in a separate process, bound it to only 2 CPU cores to minimise its impact on resources (useful if the script is heavy on the CPU), and then waited for that process to complete before continuing all in one command line. Used in a script, this would be an elegant way to sequence a set of tasks, and also bind or isolate any of those tasks to a specific set of CPU’s.

Thank you for taking the time to join me off the shell, if you would like to use Set-ProcessCPUAffinity, you can find it on my Github Repo (below). If there’s anyone or any groups you think would benefit from this post, or may have something to say about it, please feel free to share it around.

Now I need to work out if I’m going to go Jet Ski, Snowboarding, Tramping, Mountain Biking, Luging, Quad Biking… So much to do, not enough days to do it in!

Adam – Off the Shell.

Subscribe to Off the ShellOff the Shell, direct to your inbox!
Share with a peer!

Add a Comment

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.