在 NUMA 架构中按线程移动内存页

Posted

技术标签:

【中文标题】在 NUMA 架构中按线程移动内存页【英文标题】:Move memory pages per-thread in NUMA architecture 【发布时间】:2013-08-02 05:14:28 【问题描述】:

我有两个问题:

(i) 假设线程 X 在 CPU Y 上运行。是否可以使用系统调用 migrate_pages - 甚至更好的 move_pages(或它们的 libnuma 包装器) - 将与 X 关联的页面移动到连接 Y 的节点?

之所以出现这个问题,是因为两个系统调用的第一个参数都是 PID(而且我需要一个每个线程的方法来进行一些研究)

(ii) 在 (i) 的肯定回答的情况下,我怎样才能获得某个线程使用的所有页面?我的目标是,例如移动包含数组 M[] 的页面...如何将数据结构与其内存页面“链接”,以便使用上面的系统调用?

额外信息:我正在使用 C 和 pthreads。提前致谢!

【问题讨论】:

一般来说,当内核系统调用记录为采用“pid”参数时,您应该不信任手册并进行更多研究。几乎所有这样的系统调用实际上都采用 tids。不幸的是,很多文档都是由不知道区别的人编写的...... 非常感谢..我尝试了 TID 并且它有效! 【参考方案1】:

您想使用更高级别的libnuma 接口而不是低级别的系统调用。

libnuma 库为 Linux 内核支持的 NUMA(非统一内存访问)策略提供了一个简单的编程接口。在 NUMA 架构上,某些内存区域的延迟或带宽与其他区域不同。

可用的策略是页面交错(即,以循环方式从系统上的所有或子集节点分配)、首选节点分配(即,最好在特定节点上分配)、本地分配(即,在当前正在执行任务的节点上分配),或仅在特定节点上分配(即,在可用节点的某个子集上分配)。也可以将任务绑定到特定节点。

man pages for the low level numa_* system calls 警告您不要使用它们:

-lnuma 链接以获取系统调用定义。 libnuma 和所需的 <numaif.h> 标头在 numactl 包中提供。

但是,应用程序不应直接使用这些系统调用。相反,建议使用numactl 包中的numa(3) 函数提供的更高级别的接口。 numactl 软件包可在 <ftp://oss.sgi.com/www/projects/libnuma/download/> 获得。该软件包也包含在一些 Linux 发行版中。某些发行版在单独的 numactl-devel 包中包含开发库和标头。

【讨论】:

好吧...我明白了。然而,系统调用的优点是我不需要链接到 -lnuma。 @guipy:无论你的船漂浮什么。我个人认为这与直接调用 sbrk() 一样有利,这样我就可以避免链接到 C 库。【参考方案2】:

这是我用于将线程固定到单个 CPU 并将堆栈移动到相应的 NUMA 节点的代码(稍作调整以删除其他地方定义的一些常量)。注意我先正常创建线程,然后在线程内部调用下面的SetAffinityAndRelocateStack()。我认为这比尝试创建自己的堆栈要好得多,因为堆栈具有特殊的增长支持,以防达到底部。

代码也可以适应从外部对新创建的线程进行操作,但这可能会导致竞争条件(例如,如果线程对其堆栈执行 I/O),所以我不推荐它。

void* PreFaultStack()

    const size_t NUM_PAGES_TO_PRE_FAULT = 50;
    const size_t size = NUM_PAGES_TO_PRE_FAULT * numa_pagesize();
    void *allocaBase = alloca(size);
    memset(allocaBase, 0, size);
    return allocaBase;


void SetAffinityAndRelocateStack(int cpuNum)

    assert(-1 != cpuNum);
    cpu_set_t cpuset;
    CPU_ZERO(&cpuset);
    CPU_SET(cpuNum, &cpuset);
    const int rc = pthread_setaffinity_np(pthread_self(), sizeof(cpu_set_t), &cpuset);
    assert(0 == rc);

    pthread_attr_t attr;
    void *stackAddr = nullptr;
    size_t stackSize = 0;
    if ((0 != pthread_getattr_np(pthread_self(), &attr)) || (0 != pthread_attr_getstack(&attr, &stackAddr, &stackSize))) 
        assert(false);
    

    const unsigned long nodeMask = 1UL << numa_node_of_cpu(cpuNum);
    const auto bindRc = mbind(stackAddr, stackSize, MPOL_BIND, &nodeMask, sizeof(nodeMask), MPOL_MF_MOVE | MPOL_MF_STRICT);
    assert(0 == bindRc);

    PreFaultStack();
    // TODO: Also lock the stack with mlock() to guarantee it stays resident in RAM
    return;

【讨论】:

以上是关于在 NUMA 架构中按线程移动内存页的主要内容,如果未能解决你的问题,请参考以下文章

Linux 操作系统原理 — 进程管理 — NUMA 架构中的多线程调度开销与性能优化

omp 刷新和 cc-NUMA 架构

如何计算对远程 NUMA 内存节点的内存访问?

如何将所有内存分配限制到一个 NUMA 节点

用于测量 Linux 中 NUMA 节点缓存未命中/命中的工具?

剩余内存无法满足申请时,系统会怎么做?