CorePrio

Free and Open Source

CorePrio

A free, open source CPU optimization utility for the AMD Threadripper 2990WX and 2970WX on Windows. It offers two features: Our implementation of AMD’s Dynamic Local Mode, and our NUMA Dissociater.

Dynamic Local ModeNUMA DissociaterCorePrio UsageRevision History

CorePrio user interface
The CorePrio user interface.

Two Features

What CorePrio Does

Dynamic Local Mode

More configurable and robust than AMD’s implementation: Set the prioritized CPU affinity, the (software) thread count, the refresh rate, and which processes to include or exclude. Conceived for the Threadripper 2990WX and 2970WX and their asymmetric die performance; there are potentially other use cases.

How Dynamic Local Mode works

NUMA Dissociater

An experimental feature, confirmed to work on the EPYC 7551 and Threadripper 2970WX and 2990WX. It may work on other HCC NUMA platforms.

About the NUMA Dissociater

Dynamic Local Mode

Why the 2990WX and 2970WX Need It

When the Threadripper 2990WX and 2970WX were developed, AMD had to make do with the 4 memory channels available on TR4 socket motherboards, to retain chipset compatibility. So 2 of the 4 dies in these CPUs have no memory channels attached. They must pass all memory requests through the Infinity Fabric chip interconnect.

Because of this indirect I/O access, the memory (and PCIe) latency of two dies, half the system’s cores, ends up higher than that of the other two.

By default, each die is exposed as a NUMA node. The two more optimal dies each have 2 memory channels of node local memory; the two less optimal dies have none. NUMA designs are more typically used to associate the memory closest to a CPU package, so the operating system knows which memory to use for threads running on specific CPUs.

The lack of node local memory throws Windows for a loop. It doesn’t realize it should schedule threads on the more optimal dies first, since it focuses on matching memory to CPUs. With no memory to match to the less efficient dies, it simply schedules threads equally across the entire CPU package.

Pending scheduler work from Microsoft that may never come, AMD developed a workaround called Dynamic Local Mode. It monitors running threads and suggests to the OS that those with the highest activity be scheduled on the more efficient dies. AMD’s Dynamic Local Mode is included with their Ryzen Master software.

We developed our own implementation of Dynamic Local Mode, which allows tuning and customization beyond what AMD’s offering can do.

AMD Threadripper 2990WX architecture: four dies, with DDR4 memory channels attached to only two of them
AMD Threadripper 2990WX architecture.

How It Works

CorePrio monitors all threads on the system and moves those with the highest activity to the more optimal CPU dies, while still letting Windows choose exactly where on those dies to schedule them. When a thread’s activity diminishes, its affinity is changed back to all dies.

Since no hard CPU affinity is set, applications are still free to expand across the entire CPU. That’s why we call it a prioritized soft CPU affinity.

Dynamic Local Mode is designed for loads that don’t max out all available CPU cores. Applications that put a full load across the entire CPU could perform marginally worse, because when the whole CPU is loaded there are no cores to prioritize threads to. It may be best to exclude some highly multi-threaded applications.

You can see details of CorePrio’s thread management with the console utility, or with an elevated instance of DebugView with Capture Global Win32 enabled.

Prioritized affinity
The more optimal CPU cores.
Thread count
The number of threads moved to the prioritized cores at any given time.
Refresh rate
How often CorePrio makes these changes.

NUMA Dissociater

An Experimental Fix for a Curious Slowdown

This is a curious discovery, and a feature still under investigation.

Wendell at Level1Techs bought an EPYC 7551 and found the same sub-optimal benchmark results seen on the 2990WX. When the EPYC system was set to UMA mode, the performance issues disappeared, and certain benchmarks, specifically Indigo and 7-Zip, saw 2X gains, bringing them up to par with their theoretical performance based on core count and with the performance seen on Linux.

This led Wendell to believe the issues on the 2990WX were not solely due to 2 dies having no direct memory channels, and that the same effect was being seen on both platforms.

Back in NUMA mode, he noticed that changing the CPU affinity of select benchmarks (such as Indigo) made them mysteriously faster. That explains the gains he saw earlier after removing core 0 in 2990WX Indigo benchmarks. Ian at AnandTech later tested core 0 removal on the 2990WX and didn’t see the same impact: The CPU affinity has to be changed after the process starts. The effect isn’t seen when the affinity is assigned during process creation.

Dumping thread information in real time showed that after the fix is applied, thread ideal processors are doled out differently, no longer constrained to a single NUMA node. That alone shouldn’t cause a problem, since ideal processors are supposed to be only a hint to the scheduler.

2990WX Indigo performance regression
2990WX Indigo performance regression. Image from Hexus.net.

What Is the Issue?

Nobody knows, or at least nobody has said. It could be core thrashing, or a memory channel bottleneck inherent to the NUMA allocation strategy. No theory has panned out to a definitive cause, which leads to speculation that it may be a bug or quirk deep in the Windows scheduler.

What Is the Fix?

The fix is bizarrely imprecise. For affected processes, a call to SetProcessAffinityMask, without even changing the affinity (all CPUs to all CPUs), resolves the performance issue, at least most of the time. Our best guess is that it removes the process’s preferred NUMA node, which changes the Windows scheduler’s behavior, as shown by the thread ideal processor selections and, more importantly, the massive change in performance. The NUMA Dissociater applies this, with ~100% gains in Indigo and select other benchmarks.

Which CPUs Are Affected?

The Threadripper 2990WX and 2970WX and the EPYC 7551 are confirmed. It isn’t clear whether this applies only to AMD processors. Hopefully the community can contribute to our understanding of the details and scope of this behavior.

Consistency

The NUMA Dissociater doesn’t always take, particularly on the 2990WX; EPYC is less affected. On the 2990WX, if the Indigo process is restarted 10 times, about half of those runs won’t be the fast one. That’s still a massive improvement, since without the NUMA Dissociater every run is slow. This isn’t a bug in CorePrio but something inherent to the performance issue, and as we learn more we expect to improve consistency.

Usage

CorePrio Usage

CorePrio can run as a system service with a managing user interface, or as a stand-alone console mode utility.

The CorePrio installation package contains:

  • corepriow.exeThe GUI, resident in the system tray.
  • cpriosvc.exeThe CorePrio service.
  • coreprio.exeThe CorePrio console mode utility.

Launch the GUI from the Start menu or by running corepriow.exe. It lets you start and stop the service and tweak its parameters, and it resides in the system tray as a CPU icon.

The prioritized affinity defaults to the first half of the logical cores, and the number of threads to prioritize defaults to half the total logical core count (or the number of logical CPUs in the prioritized affinity).

Threads to prioritize (formerly thread count) is the maximum number of software threads moved to the prioritized CPU set at any given time. It should generally not exceed the number of CPU cores in the prioritized CPU affinity mask.

Limited debug output from the service can be viewed with an elevated instance of DebugView with Capture / Capture Global Win32 checked. To see output, the console mode utility is recommended.

CorePrio console output
CorePrio console output.

Console Mode

Run coreprio.exe without parameters to use the defaults or saved settings; command line parameters override them. Running the console utility stops the CorePrio service, and the same algorithm runs in the console instead, so the output you see is from the console app, not the service.

USAGE: CorePrio [-i refresh_rate_ms] [-n sw_thread_count] [-f] [-a prioritized_affinity]
                [-l,--inclusions include_pattern] [-x,--exclusions exclude_pattern]
Defaults:
            -i (rate) -> 500 (ms)
            -n (threads) -> half of logical core count
            -a (affinity) -> first half of logical cores
            -l,--inclusions -> '*'
            -x,--exclusions -> ''
Inclusion and exclusion patterns are semicolon ';' delimited. Inclusions processed first, then exclusions.
The -f parameter enforces an experimental performance fix for Windows NUMA and disables DLM.

Example:

CorePrio -i 500 -n 32 -a 00000000ffffffff --inclusions * --exclusions someapp.exe;more*.exe

Runs CorePrio at a refresh rate of 500 ms, managing 32 threads, with a prioritized CPU affinity of 0-31. It includes all processes (*) and excludes those matching someapp.exe or more*.exe.

Support This Work

CorePrio is freeware. You can support it, and the continued R&D of other Bitsum projects, by buying a license for Process Lasso Pro, or by donating any amount.


Discover more from Bitsum

Subscribe to get the latest posts sent to your email.

Bitsum Technologies

Independent Windows software since 2002. We build novel solutions to software performance and automation challenges.