(对于Linux平台)尝试通过不同NUMA节点上的进程之间的环回接口进行通信(以同步方式)可行吗?
如果进程位于同一个NUMA节点上,该怎么办?我知道可以内存绑定进程和/或将CPU关联设置为节点(使用libnuma)。我不知道这是否也适用于网络接口。
稍后编辑如果环回接口只是内核使用的内存缓冲区,是否有方法确保缓冲区位于同一NUMA节点上,以便两个进程在没有跨节点开销的情况下进行通信?
网络接口不驻留在节点上;它们是在整个机器上共享的虚拟或真实设备。环回接口只是某个地方的内存缓冲区和一些内核代码。运行以支持该设备的代码可能会像系统中的任何其他线程一样在CPU内核中来回跳动。
您谈到了NUMA节点,并用Linux标记了这个问题。Linux不是在纯NUMA体系结构上运行,而是在SMP体系结构上。英特尔、AMD、ARM等现代CPU都使用独立的内核、不同程度的缓存/内存接口统一以及内核或CPU之间的高速串行链路来合成SMP硬件环境。实际上,运行在上面的操作系统或软件不可能看到底层的NUMA架构;它认为它运行在一个经典的SMP体系结构上。
英特尔/AMD/其他所有人都这么做了,因为在过去,成功的多CPU机器实际上是SMP;它们有多个共享同一内存总线的CPU,并且可以平等地访问总线另一端的RAM。编写软件就是为了利用这一点(Linux、Windows等)。
然后,CPU制造商意识到SMP体系结构在速度改进方面很糟糕。AMD首先眨眼,放弃SMP,转而支持Hypertransport,并取得了成功。英特尔坚持使用纯SMP的时间更长,但很快也放弃了,开始在CPU之间使用QPI。
但为了使旧软件(Linux、Windows等)具有向后兼容性,CPU设计者不得不在Hypertransport和QPI之上创建一个合成SMP硬件环境。原则上,在那个时候,他们可能已经决定SMP已经死了,并为我们提供了纯NUMA架构。但这很可能是商业自杀;这需要整个硬件和软件行业的合作才能同意走这条路,但到那时,从头开始重写一切已经太晚了。
像网络套接字(包括通过环回接口)、管道、串行端口这样的想法是不同步的。它们是流载体,发送方和接收方不通过传输数据来同步。也就是说,发送方可以write()
数据,并认为它已经完成,但实际上数据仍然卡在某个网络缓冲区中,并且还没有进入目的进程必须调用的read()
来接收数据。
Linux对进程和线程所做的就是努力一次运行所有进程和线程,达到机器中CPU核心数量的限制。总的来说,这将导致您的进程在不同的核心上同时运行。我认为Linux还将使用哪个物理CPU的内存保存进程的大部分数据的知识,并将尝试在该CPU上运行进程;这样的话,内存延迟会稍微好一点。
如果您的进程试图通过套接字、管道或类似方式进行通信,则会导致数据从一个进程的内存空间复制到由内核控制的内存缓冲区(这就是write()
在后台所做的),然后从中复制到接收进程的内存空间(read()
就是这样做的)。中间内核缓冲区实际在哪里并不重要,因为在微电子级别(SMP级别以下)发生的事务基本相同。内存分配和进程可以绑定到特定的CPU内核,但你不能影响内核将其内存缓冲区放在哪里,交换的数据必须通过这些缓冲区
关于内存和进程核心亲和力,要做到这一点真的很难获得任何可衡量的好处。现在的操作系统非常善于理解CPU的行为,所以最好让操作系统在它选择的任何地方运行你的进程和内核。像英特尔这样的公司为Linux项目做出了大量的代码贡献,特别是为了确保Linux在最新、最棒的芯片上尽可能地做到这一点。
==编辑==根据引人入胜的评论添加内容!
我所说的"纯NUMA"实际上是指一个CPU内核不能直接寻址物理连接到另一个CPU核心的内存的系统。这些系统包括Transputer,甚至索尼PS3中的Cell处理器。这些不是SMP,硅中没有任何东西可以将单独的存储器统一到一个地址空间中,所以缓存一致性的问题就不存在了
对于Transputer系统,访问连接到另一个Transputer的存储器的唯一方法是让应用软件通过串行链路发送数据;它之所以成为CSP,是因为发送应用程序将完成发送,直到接收应用程序读取最后一个字节。
对于Cell处理器,有8个数学核心,每个核心有256k字节的RAM。这是数学核心能够处理的唯一RAM。要使用它们,应用程序必须将数据和代码移动到256k的RAM中,告诉核心运行,然后将结果移出(可能回到RAM,或另一个数学核心)。
今天有一些超级计算机与此并不相似。K机器(日本的Riken、Kobe)有很多内核,是一个非常复杂的片上互连结构,应用程序使用OpenMPI在节点之间移动数据;节点不能直接寻址其他节点上的内存。
关键是,在PS3上,由应用软件决定什么数据在什么内存中,什么时候在什么内存,而英特尔和AMD的现代x86实现使所有内存中的所有数据(无论它们是通过L3缓存共享的,还是在超传输或QPI链路的另一端远程的)都可以从任何核心访问(这就是SMP的含义)。
在Cell过程中编写的代码的全面性能对于瓦特数和晶体管数来说确实令人震惊。问题是,在一个程序员接受SMP环境写作培训的世界里,需要进行大脑移植才能掌握非SMP环境。
Rust和Go等较新的语言重新引入了通信顺序过程的概念,这是20世纪80年代和90年代初Transputer的全部概念。CSP几乎是多核系统的理想选择,因为硬件不需要实现SMP环境。原则上,这节省了大量的硅。
在像C这样的语言中,在当今缓存一致性SMP芯片之上实现的CSP通常涉及一个线程将数据写入缓冲区,然后将数据复制到属于另一个线程的缓冲区(Rust可以做得有点不同,因为Rust知道内存所有权,所以可以转移所有权而不是复制内存。我不熟悉Go,也许它也可以这样做)。
从微电子层面来看,将数据从一个缓冲区复制到另一个缓冲区时,与数据由两个核心共享而不是复制时的情况没有什么不同(尤其是在AMD的超传输CPU中,每个CPU都有自己的存储系统)。为了共享数据,远程核心必须使用超传输从另一个核心的内存请求数据,并使用更多流量来保持缓存一致性。这大约相当于将数据从一个核心复制到另一个核心的超传输流量,但随后没有缓存一致性流量。