【问题标题】:memory allocation and access on NUMA hardwareNUMA 硬件上的内存分配和访问
【发布时间】:2011-12-08 16:49:51
【问题描述】:

我正在用 python 开发一个科学计算工具,它应该能够在 NUMA 共享内存环境中将工作分配到多个内核上。我正在寻找最有效的方法。

由于 python 的全局解释器锁,线程被排除在游戏之外,这使我唯一的选择是分叉。对于进程间通信,我想我的选择是管道、套接字或 mmap。如果此列表中缺少内容,请指出。

我的应用程序需要在进程之间进行相当多的通信,并需要访问一些公共数据。我主要担心的是延迟。

我的问题:当我 fork 一个进程时,它的内存是否会位于分配给它的核心附近?作为 *nix 副本中的 fork,最初我认为情况并非如此。我是否要强制复制以更快地访问内存,如果是这样,最好的方法是什么?如果我使用 mmap 进行通信,该内存是否仍可以分布在内核上,还是位于单个内核上?是否有透明地重新定位数据以优化访问的流程?有没有办法直接控制物理分配,或请求分配信息以帮助优化?

在更高的层次上,哪些是我的硬件决定的,哪些是由操作系统决定的?我正在购买一台高端多路机器,并在 AMD Opteron 和 Intel Xeon 之间犹豫不决。特定硬件对上述任何问题有何影响?

【问题讨论】:

  • 如果代码是用 Python 编写的,那么担心这个级别的性能是愚蠢的——解释器的开销很可能会淹没 NUMA 所做的任何差异。
  • Python 可以调用用 C 编写的例程,例如 numpy。此外,Pypy 使 python 脚本相当高效。具有共享内存的多个进程如何与 NUMA 交互的问题也是一个有趣的问题。
  • 确实,我认为 Python 是一种方便的语言,可以灵活地将 C 代码片段粘合在一起。如果以正确的方式完成,我相信解释器的开销可以保持在较低水平。

标签: python ipc fork shared-memory numa


【解决方案1】:

由于 GIL 是 Python 的致命弱点之一,因此它提供了更好的多进程支持。例如有队列、管道、锁、共享值和共享数组。还有一种称为管理器的东西,它允许您包装大量 Python 数据结构并以 IPC 友好的方式共享它们。我想这些大部分都是通过管道或套接字工作的,但我没有深入研究内部结构。

http://docs.python.org/2/library/multiprocessing.html

Linux 如何为 NUMA 系统建模?

内核检测到它在多核机器上运行,然后检测有多少硬件以及拓扑是什么。然后它使用节点的想法创建此拓扑的模型。节点是一个物理插槽,包含一个 CPU(可能具有多个内核)和连接到它的内存。为什么基于节点而不是基于核心?因为内存总线是将 RAM 连接到 CPU 插槽的物理线路,并且单个插槽中 CPU 上的所有内核对该内存总线上的所有 RAM 具有相同的访问时间。

一条内存总线上的内存如何被另一条内存总线上的内核访问?

在 x86 系统上,这是通过缓存实现的。现代操作系统使用称为转换后备缓冲区 (TLB) 的硬件将虚拟地址映射到物理地址。如果缓存的任务是获取的内存是本地的,则它会在本地读取。如果它不是本地的,它将通过 AMD 系统上的 Hyper Transport 总线或 Intel 上的 QuickPath 到达远程内存以满足需求。由于它是在缓存级别完成的,因此理论上您不需要了解它。你当然无法控制它。但对于高性能应用程序,了解如何最大限度地减少远程访问量非常有用。

操作系统实际上在哪里定位虚拟内存的物理页面?

当一个进程被分叉时,它会继承它的所有父页面(由于 COW)。内核知道哪个节点对于它是“首选”节点的进程是“最佳”的。这可以修改,但再次默认为与父级相同。内存分配将默认为与父节点相同的节点,除非显式更改。

是否存在移动内存的透明过程?

没有。一旦分配了内存,它就固定在分配它的节点上。您可以在另一个节点上进行新分配,移动数据,然后在第一个节点上解除分配,但这有点麻烦。

有没有办法控制分配?

默认是分配给本地节点。如果您使用 libnuma,您可以更改分配的完成方式(例如循环或交错),而不是默认为本地。

我从这篇博文中获取了很多信息:

http://blog.jcole.us/2010/09/28/mysql-swap-insanity-and-the-numa-architecture/

我绝对建议您完整阅读它以收集更多信息。

【讨论】:

    【解决方案2】:
    ##client.py
    import socket
    import sys
    
    HOST, PORT = "localhost", 9999
    data = " ".join(sys.argv[1:])
    
    
    
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    
    try:
    
        sock.connect((HOST, PORT))
        sock.sendall(bytes(data +"\n", "utf-8"))
    
        received = str(sock.recv(1024), "utf-8")
    finally:
        sock.close()
    
    print("Sent:     {}".format(data))
    print("Received: {}".format(received))
    
    ##server.py
    import socketserver
    
    class MyTCPHandler(socketserver.BaseRequestHandler):
    
        def handle(self):
    
            self.data = self.request.recv(1024).strip()
            print("{} wrote:".format(self.client_address[0]))
            print(self.data)
            self.request.sendall(self.data.upper())
    
    if __name__ == "__main__":
        HOST, PORT = "10.0.0.1", 9999
        server = socketserver.TCPServer((HOST, PORT), MyTCPHandler)
        server.serve_forever()
    

    【讨论】:

      猜你喜欢
      • 2017-07-11
      • 2011-12-30
      • 2011-04-13
      • 2012-11-28
      • 1970-01-01
      • 1970-01-01
      • 2015-03-08
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多