C# 应用如何利用 NUMA 架构进行性能调优

来源:站长平台作者:Robin头衔:草根站长
导读:本期聚焦于小伙伴创作的《C# 应用如何利用 NUMA 架构进行性能调优》,敬请观看详情,探索知识的价值。以下视频、文章将为您系统阐述其核心内容与价值。如果您觉得《C# 应用如何利用 NUMA 架构进行性能调优》有用,将其分享出去将是对创作者最好的鼓励。

NUMA 即非统一内存访问架构,是现代多处理器服务器的主流硬件设计,其核心特点是每个处理器拥有本地内存,访问本地内存的速度远快于访问其他处理器的远程内存。C# 应用运行在依托 NUMA 架构的硬件上时,如果忽略内存和线程的节点分配,很容易出现大量远程内存访问,导致性能下降。理解 NUMA 架构的特性并结合 C# 的相关 API 进行调优,是提升高负载应用性能的重要手段。

C# 应用如何利用 NUMA 架构进行性能调优

NUMA 架构基础原理

NUMA 架构将服务器划分为多个节点,每个节点包含若干个 CPU 核心和对应的本地内存。节点内的 CPU 访问本地内存的延迟通常在几十纳秒,而访问其他节点的远程内存延迟可能增加一倍甚至更多。当 C# 应用分配的内存跨节点访问时,不仅会增加内存访问耗时,还会占用节点间的互联总线带宽,影响整体性能。

在 Windows 系统中,可以通过 PowerShell 命令 Get-NumaNode 查看当前服务器的 NUMA 节点信息,包括每个节点的 CPU 核心范围和内存大小。在 Linux 系统中,可以通过 numactl -H 命令查看相关信息。

C# 中 NUMA 相关的系统 API 调用

.NET 本身没有内置直接的 NUMA 管理 API,但可以通过调用系统层面的原生 API 来实现 NUMA 相关的调优操作。以下是 Windows 系统中常用的 NUMA 相关 API 的 C# 调用示例。

获取 NUMA 节点信息

首先需要通过 P/Invoke 声明获取 NUMA 节点信息的原生函数,代码如下:

using System;
using System.Runtime.InteropServices;

public class NumaHelper
{
    // 获取当前系统的 NUMA 节点数量
    [DllImport("kernel32.dll", SetLastError = true)]
    private static extern ushort GetNumaHighestNodeNumber(out ulong highestNodeNumber);

    // 获取指定节点对应的处理器掩码
    [DllImport("kernel32.dll", SetLastError = true)]
    private static extern bool GetNumaNodeProcessorMask(ushort node, out ulong processorMask);

    // 获取指定进程当前运行的 NUMA 节点
    [DllImport("kernel32.dll", SetLastError = true)]
    private static extern bool GetProcessNumaNode(IntPtr processHandle, out ushort nodeNumber);

    // 设置当前线程的首选 NUMA 节点
    [DllImport("kernel32.dll", SetLastError = true)]
    private static extern bool SetThreadIdealProcessorEx(IntPtr threadHandle, ref PROCESSOR_NUMBER processorNumber, IntPtr previousProcessorNumber);

    [StructLayout(LayoutKind.Sequential)]
    public struct PROCESSOR_NUMBER
    {
        public ushort Group;
        public byte Number;
        public byte Reserved;
    }

    // 获取 NUMA 节点总数
    public static ulong GetNumaNodeCount()
    {
        if (GetNumaHighestNodeNumber(out ulong highestNode))
        {
            // 节点编号从0开始,所以总数为最高编号+1
            return highestNode + 1;
        }
        return 0;
    }

    // 获取指定节点的 CPU 核心掩码
    public static ulong GetNodeProcessorMask(ushort node)
    {
        if (GetNumaNodeProcessorMask(node, out ulong mask))
        {
            return mask;
        }
        return 0;
    }
}

设置进程和线程的 NUMA 亲和性

进程亲和性决定了进程可以在哪些 CPU 核心上运行,将进程绑定到同一个 NUMA 节点的核心上,可以减少远程内存访问。以下是设置进程亲和性的代码示例:

using System;
using System.Diagnostics;
using System.Runtime.InteropServices;

public class ProcessAffinityHelper
{
    // 设置进程亲和性,processorMask 为 CPU 核心掩码
    [DllImport("kernel32.dll", SetLastError = true)]
    private static extern bool SetProcessAffinityMask(IntPtr processHandle, IntPtr processorMask);

    // 获取当前进程的句柄
    public static IntPtr GetCurrentProcessHandle()
    {
        return Process.GetCurrentProcess().Handle;
    }

    // 将当前进程绑定到指定 NUMA 节点的 CPU 核心
    public static bool BindProcessToNumaNode(ushort node)
    {
        ulong processorMask = NumaHelper.GetNodeProcessorMask(node);
        if (processorMask == 0)
        {
            return false;
        }
        IntPtr handle = GetCurrentProcessHandle();
        // 将 ulong 转换为 IntPtr,注意 32 位和 64 位系统的差异
        IntPtr mask = (IntPtr)processorMask;
        return SetProcessAffinityMask(handle, mask);
    }
}

内存分配优化策略

NUMA 架构下内存分配的优化核心是尽量让内存分配在进程运行的本地节点。C# 的托管内存分配由 CLR 的垃圾回收器管理,默认情况下不会特意考虑 NUMA 节点,但可以通过以下方式间接优化:

  • 尽量在应用启动时预分配核心业务所需的大对象,减少运行时的跨节点内存分配
  • 对于需要频繁访问的大数组,可以使用 GC.TryStartNoGCRegion 方法在指定区域分配,减少 GC 带来的内存移动
  • 如果使用的是 .NET 5 及以上版本,可以开启 System.GC.NumaAware 配置,让 GC 在分配内存时考虑 NUMA 节点,该配置默认在服务器 GC 模式下开启

以下是一个预分配大对象的示例,将数组分配在指定节点的本地内存中:

public class MemoryOptimizeHelper
{
    // 预分配指定大小的字节数组,尽量分配在当前线程所在的 NUMA 节点
    public static byte[] PreAllocateLargeArray(int size)
    {
        // 先绑定当前线程到目标 NUMA 节点,再分配内存
        // 假设目标节点为 0
        ushort targetNode = 0;
        // 设置当前线程的首选处理器为节点 0 的第一个核心
        NumaHelper.PROCESSOR_NUMBER processorNumber = new NumaHelper.PROCESSOR_NUMBER
        {
            Group = 0,
            Number = 0, // 对应节点 0 的第一个核心
            Reserved = 0
        };
        // 获取当前线程句柄
        IntPtr threadHandle = System.Diagnostics.Process.GetCurrentProcess().Threads[0].ThreadHandle;
        NumaHelper.SetThreadIdealProcessorEx(threadHandle, ref processorNumber, IntPtr.Zero);
        // 分配大数组,此时内存大概率分配在节点 0 的本地内存
        return new byte[size];
    }
}

线程调度优化

线程的运行位置直接影响内存访问的效率,将线程绑定到对应的 NUMA 节点核心,可以让线程优先访问本地内存。除了前面提到的设置线程首选处理器,还可以通过以下方式优化:

  • 为每个 NUMA 节点创建独立的工作线程池,线程处理的数据尽量预分配在该节点的内存中
  • 避免线程在不同 NUMA 节点的核心之间频繁迁移,减少缓存失效和远程内存访问
  • 对于计算密集型任务,将任务拆分到不同 NUMA 节点,每个节点处理本地内存中的数据

以下是一个简单的 NUMA 感知线程池示例:

using System;
using System.Collections.Generic;
using System.Threading;

public class NumaAwareThreadPool
{
    private Dictionary<ushort, List<Thread>> nodeThreads = new Dictionary<ushort, List<Thread>>();

    // 初始化线程池,为每个 NUMA 节点创建指定数量的工作线程
    public void Initialize(ushort threadCountPerNode)
    {
        ulong nodeCount = NumaHelper.GetNumaNodeCount();
        for (ushort node = 0; node < nodeCount; node++)
        {
            List<Thread> threads = new List<Thread>();
            for (int i = 0; i < threadCountPerNode; i++)
            {
                Thread thread = new Thread(WorkerMethod);
                thread.Start(node);
                threads.Add(thread);
            }
            nodeThreads[node] = threads;
        }
    }

    // 工作线程方法,接收对应的 NUMA 节点编号
    private void WorkerMethod(object obj)
    {
        ushort node = (ushort)obj;
        // 绑定线程到对应 NUMA 节点的核心
        ulong processorMask = NumaHelper.GetNodeProcessorMask(node);
        // 这里省略线程亲和性设置的具体代码,可参考前面的示例
        // 线程循环处理该节点对应的本地任务
        while (true)
        {
            // 处理任务逻辑
            Thread.Sleep(100);
        }
    }
}

调优效果验证

完成 NUMA 调优后,需要验证调优效果。可以通过以下方式对比调优前后的性能:

  • 使用性能计数器监控应用的 CPU 使用率、内存访问延迟
  • 通过 numactl --hardware(Linux)或性能监视器(Windows)查看节点的内存访问分布,确认远程内存访问占比是否下降
  • 运行核心业务场景的压测,对比调优前后的吞吐量、响应时间

实际测试表明,对于内存访问密集型的 C# 应用,合理的 NUMA 调优可以带来 10% 到 30% 的性能提升,尤其是在高并发、大数据量处理的场景下效果更为明显。

NUMA C# 性能调优 进程亲和性修改时间:2026-06-12 18:27:24

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。