7

在大量内核上调试我的程序时,我遇到了非常奇怪的insufficient virtual memory. 我的调查导致代码和平,主人向每个奴隶发送小消息。然后我写了一个小程序,其中 1 个 master 简单地发送 10 个整数,MPI_SEND所有的 slave 用MPI_RECV. /proc/self/status之前和之后的文件比较MPI_SEND显示,内存大小之间的差异是巨大的!最有趣的事情(这使我的程序崩溃)是该内存不会在之后释放MPI_Send并且仍然占用大量空间。

有任何想法吗?

 System memory usage before MPI_Send, rank: 0
Name:   test_send_size                                                                                
State:  R (running)                                                                                  
Pid:    7825                                                                                           
Groups: 2840                                                                                        
VmPeak:   251400 kB                                                                                 
VmSize:   186628 kB                                                                                 
VmLck:        72 kB                                                                                  
VmHWM:      4068 kB                                                                                  
VmRSS:      4068 kB                                                                                  
VmData:    71076 kB                                                                                 
VmStk:        92 kB                                                                                  
VmExe:       604 kB                                                                                  
VmLib:      6588 kB                                                                                  
VmPTE:       148 kB                                                                                  
VmSwap:        0 kB                                                                                 
Threads:    3                                                                                          

 System memory usage after MPI_Send, rank 0
Name:   test_send_size                                                                                
State:  R (running)                                                                                  
Pid:    7825                                                                                           
Groups: 2840                                                                                        
VmPeak:   456880 kB                                                                                 
VmSize:   456872 kB                                                                                 
VmLck:    257884 kB                                                                                  
VmHWM:    274612 kB                                                                                  
VmRSS:    274612 kB                                                                                  
VmData:   341320 kB                                                                                 
VmStk:        92 kB                                                                                  
VmExe:       604 kB                                                                                  
VmLib:      6588 kB                                                                                  
VmPTE:       676 kB                                                                                  
VmSwap:        0 kB                                                                                 
Threads:    3        
4

2 回答 2

10

这是几乎所有在 InfiniBand 上运行的 MPI 实现的预期行为。IB RDMA 机制要求应注册数据缓冲区,即首先将它们锁定到物理内存中的固定位置,然后驱动程序告诉 InfiniBand HCA 如何将虚拟地址映射到物理内存。注册内存以供 IB HCA 使用是非常复杂且因此非常缓慢的过程,这就是为什么大多数 MPI 实现从不取消注册曾经注册的内存,希望稍后将相同的内存用作源或数据目标。如果注册的内存是堆内存,它永远不会返回给操作系统,这就是为什么你的数据段只会增加大小。

尽可能重用发送和接收缓冲区。请记住,通过 InfiniBand 进行的通信会产生高内存开销。大多数人并没有真正考虑过这一点,并且通常没有很好的文档记录,但是 InfiniBand 使用了许多特殊的数据结构(队列),这些数据结构被分配在进程的内存中,并且这些队列随着进程的数量而显着增长。在某些完全连接的情况下,队列内存量可能非常大,以至于实际上没有内存可供应用程序使用。

有一些参数可以控制英特尔 MPI 使用的 IB 队列。在您的情况下,最重要的是I_MPI_DAPL_BUFFER_NUM控制预分配和预注册内存的数量。它的默认值为16,因此您可能需要减小它。请注意可能的性能影响。您还可以尝试通过设置为来使用动态预分配缓冲区I_MPI_DAPL_BUFFER_ENLARGEMENT大小1。启用此选项后,英特尔 MPI 将首先注册小缓冲区,然后在需要时增加它们。另请注意,IMPI 会延迟打开连接,这就是为什么您仅在调用MPI_Send.

如果不使用 DAPL 传输,例如使用ofa传输,则您无能为力。I_MPI_OFA_USE_XRC您可以通过设置来启用 XRC 队列1。这应该以某种方式减少使用的内存。如果您的程序的通信图没有完全连接(完全连接的程序是每个等级与所有其他等级对话的程序),那么通过设置I_MPI_OFA_DYNAMIC_QPS为启用动态队列对创建可能会减少内存使用量。1

于 2012-10-26T15:20:17.627 回答
5

Hristo 的回答大多是正确的,但由于您使用的是小消息,所以会有一点不同。消息最终到达急切路径:它们首先被复制到一个已经注册的缓冲区,然后该缓冲区用于传输,接收者将消息从其末端的急切缓冲区中复制出来。在代码中重用缓冲区只会帮助处理大消息。

这样做正是为了避免注册用户提供的缓冲区的缓慢。对于大消息,复制所需的时间比注册时间长,因此使用会合协议。

这些急切的缓冲区有些浪费。例如,在英特尔 MPI 上,它们默认为 16kB,带有 OF 动词。除非使用消息聚合,否则每条 10 整数大小的消息都会占用四个 4kB 页面。但是无论如何,在与多个接收者交谈时,聚合都无济于事。

那么该怎么办?减小急切缓冲区的大小。这是通过设置急切/集合阈值(I_MPI_RDMA_EAGER_THRESHOLD环境变量)来控制的。试试 2048 甚至更小。请注意,这可能会导致延迟增加。或者更改I_MPI_DAPL_BUFFER_NUM变量来控制这些缓冲区的数量,或者尝试 Hristo 建议的动态调整大小功能。这假设您的 IMPI 正在使用 DAPL(默认值)。如果您直接使用 OF 动词,DAPL 变量将不起作用。


编辑:所以让它运行的最终解决方案是设置I_MPI_DAPL_UD=enable. 我可以推测魔法的起源,但我无法访问英特尔的代码来实际确认这一点。

IB可以有不同的传输模式,其中两种是RC(Reliable Connected)和UD(Unreliable Datagram)。RC 需要主机之间的显式连接(如 TCP),并且每个连接都会消耗一些内存。更重要的是,每个连接都有与之相关的急切缓冲区,这确实加起来了。这是您使用英特尔的默认设置所获得的。

有一个优化可能:在连接之间共享急切的缓冲区(这称为 SRQ - 共享接收队列)。还有一个名为 XRC (eXtended RC) 的 Mellanox 专用扩展,它进一步实现了队列共享:在同一节点上的进程之间。默认情况下,英特尔的 MPI 通过 DAPL 访问 IB 设备,而不是直接通过 OF 动词。我的猜测是这排除了这些优化(我没有使用 DAPL 的经验)。可以通过设置I_MPI_FABRICS=shm:ofa和启用 XRC 支持I_MPI_OFA_USE_XRC=1(使英特尔 MPI 使用 OFA 接口而不是 DAPL)。

当您切换到 UD 传输时,您会在缓冲区共享之上获得进一步优化:不再需要跟踪连接。在这个模型中,缓冲区共享是很自然的:因为没有连接,所有的内部缓冲区都在一个共享池中,就像 SRQ 一样。因此,可以进一步节省内存,但要付出一定的代价:数据报传递可能会失败,这取决于软件,而不是 IB 硬件来处理重传。当然,这对于使用 MPI 的应用程序代码来说都是透明的。

于 2012-10-26T15:40:25.407 回答