NumaScheduler 1.0.0
dotnet add package NumaScheduler --version 1.0.0
NuGet\Install-Package NumaScheduler -Version 1.0.0
<PackageReference Include="NumaScheduler" Version="1.0.0" />
<PackageVersion Include="NumaScheduler" Version="1.0.0" />
<PackageReference Include="NumaScheduler" />
paket add NumaScheduler --version 1.0.0
#r "nuget: NumaScheduler, 1.0.0"
#:package NumaScheduler@1.0.0
#addin nuget:?package=NumaScheduler&version=1.0.0
#tool nuget:?package=NumaScheduler&version=1.0.0
NumaScheduler
A cross-platform .NET TaskScheduler that pins worker threads to NUMA nodes, keeping task execution and memory access local to the same CPU socket. Supports Windows, Linux, and falls back gracefully on macOS and other platforms.
Why NUMA-aware scheduling?
On multi-socket servers, accessing memory attached to a remote NUMA node is significantly slower than accessing local memory. By keeping threads and their data on the same node you reduce cross-socket memory traffic and improve throughput and latency for data-intensive workloads.
Supported platforms
| Platform | Topology discovery | Thread pinning |
|---|---|---|
| Windows | GetNumaNodeProcessorMaskEx (kernel32) |
SetThreadGroupAffinity |
| Linux | /sys/devices/system/node/ (sysfs) |
sched_setaffinity |
| macOS / other | Single virtual node | No-op (graceful fallback) |
Target frameworks
netstandard2.0 · net8.0 · net9.0 · net10.0
Installation
dotnet add package NumaScheduler
Quick start
1. Inspect topology
var topology = NumaTopology.Instance;
Console.WriteLine($"NUMA nodes: {topology.NodeCount}, IsNumaSystem: {topology.IsNumaSystem}");
foreach (var node in topology.Nodes)
Console.WriteLine(node); // "NUMA Node 0 | 16 logical CPU(s) | ..."
2. Policy-driven scheduler (recommended)
// One thread pool per NUMA node; tasks routed to the calling thread's local node.
using var scheduler = new NumaAwareTaskScheduler(NumaSchedulingPolicy.LocalityFirst);
var task = Task.Factory.StartNew(() =>
{
// Runs on the NUMA node closest to the enqueueing thread.
}, CancellationToken.None, TaskCreationOptions.PreferFairness, scheduler);
await task;
Available policies:
| Policy | Description |
|---|---|
RoundRobin |
Distribute tasks evenly across all nodes |
LocalityFirst |
Route to the enqueueing thread's own node (default for Shared) |
LeastLoaded |
Route to the node whose queue is shortest |
3. Pin a task to a specific node
// Bypass the policy and force execution on node 1.
await NumaAwareTaskScheduler.Shared.RunOnNode(nodeIndex: 1, () =>
{
ProcessChunk(data);
});
// Generic overload returns a result.
double result = await NumaAwareTaskScheduler.Shared.RunOnNode(1, () => ComputeSum(data));
4. Extension methods
// Run on a specific node using the global Shared scheduler.
await ((Action)(() => Process(data))).RunOnNumaNode(nodeIndex: 0);
// Use LocalityFirst policy via the global Shared scheduler.
await ((Action)(() => Process(data))).RunNumaLocal();
5. Standalone per-node scheduler
Useful when you need a plain TaskScheduler reference for an API that doesn't accept NumaAwareTaskScheduler.
var node = NumaTopology.Instance.Nodes[0];
using var nodeScheduler = new NumaNodeScheduler(node, threadCount: 4);
await Task.Factory.StartNew(() => Work(), CancellationToken.None,
TaskCreationOptions.None, nodeScheduler);
6. Process-wide singleton
NumaAwareTaskScheduler.Shared is a lazily-initialised process-lifetime singleton using LocalityFirst.
Do not dispose it.
await Task.Factory.StartNew(Work, CancellationToken.None,
TaskCreationOptions.PreferFairness, NumaAwareTaskScheduler.Shared);
Diagnostics
foreach (var (nodeId, pending) in scheduler.GetNodeStats())
Console.WriteLine($"Node {nodeId}: {pending} pending tasks");
Thread safety
All public types are thread-safe. Dispose may be called from any thread; subsequent calls are no-ops.
License
MIT © 2026 Zoltan Csizmadia
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
| .NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.0 is compatible. netstandard2.1 was computed. |
| .NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen40 was computed. tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.0
- No dependencies.
-
net10.0
- No dependencies.
-
net8.0
- No dependencies.
-
net9.0
- No dependencies.
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.0.0 | 105 | 5/7/2026 |
Initial release.