禁用 mmap 和 memory trip 来加速 MPI RMA

这两天遇到一个小坑,和 MPI & Linux 内存管理有关。

起因是我写了一些 MPI RMA 函数,但是发现有两个部分的性能有问题:第一个部分是少量 MPI_Get() ,每次获取大块数据;第二个部分是大量 MPI_Put(),每次更新小块数据。这两个部分都会被调用多次。如果我不在代码里加上

1
2
mallopt(M_MMAP_MAX, 0);
mallopt(M_TRIM_THRESHOLD, -1);

或者在设环境变量

1
2
export MALLOC_MMAP_MAX_=0
export M_TRIM_THRESHOLD_=-1

那么这两个部分就会慢很多。找人问了一下以后才知道这和 Linux 的内存管理以及 MPI 对应不同硬件的实现有关。

先讲 Linux 内存管理上的原因。我看了一下Linux内存分配小结 以及 Advanced Memory Allocation, 大概明白了是怎么回事(我没学过 OS 的课,惭愧……)。M_MMAP_MAX 设为 0 以后,程序就不会通过 mmap 来申请内存了,所有的申请都通过 brk 来进行。通过 brk 申请的内存,用 free 释放以后并不会马上还给 OS,依旧留在堆内,等符合条件了再合并还给 OS。M_TRIM_THRESHOLD 用于控制堆顶端有多少空闲内存的时候触发内存紧缩(memory trim),将那些空闲内存还给 OS,设成 -1 以后在程序运行期间就不会将堆顶空闲内存还给 OS,而是留待有符合要求的 malloc 时直接从堆的空闲块中进行分配。由于申请内存以后 Linux 内核并不直接马上分配物理内存,只有到第一次操作内存发生页中断的时候才实际分配物理内存(这也就是为什么可以用 first touch policy + thread affinity 来优化 NUMA 架构上的多线程程序)。设 M_TRIM_THRESHOLD_=-1 使得频繁进行内存申请和释放的时候可能可以减少系统内核调用,从而提高速度。

再讲 MPI 部分,参考 Buffering and Message Protocols。MPI 有两种通信模式:小鹰(eager)和约会(rendezvous)。前者用于比较短的消息,后者用于比较长的消息。MPI 传输的消息分为两个部分:envelop 和 data。envelop 包括消息的 metadata,主要是收发方、信息长度、communicator 这些信息;data 则是程序需要发送的数据。小鹰模式下发送方假设接收方的 MPI 内部缓存足够容纳,因此会直接发送 envelop 和 data。约会模式下发送方需要先发送 envelop 信息,等接受方就绪并回复发送许可以后再发送 data。MPI_Put() 和小鹰模式类似,MPI_Get() 和约会模式类似。这些 RMA 函数都有比较复杂的内部缓存区和工作原理,估计是少不了申请和释放内存以调整缓冲区的。Intel 的人给我的邮件回复里提到一句『If malloc uses mmap then freeing a large allocation will evict the pages from the registration cache and require a new rendezvous to set them up 』。因此,他指出在 Cray 的超算和使用 InfiniBand 的集群上,前文提到的两个设置可以加速 MPI RMA 函数的执行速度。