现代 CPU 1在本地处理 RAM,并使用单独的通道2在它们之间进行通信。这是 NUMA 架构的消费者级版本,十多年前为超级计算机创建。
这个想法是为了避免可能导致严重争用的共享总线(旧的 FSB),因为每个内核都使用它来访问内存。随着您添加更多 NUMA 单元,您将获得更高的带宽。不利的一面是,从 CPU 的角度来看,内存变得不均匀:一些 RAM 比其他 RAM 更快。
当然,现代操作系统调度程序是 NUMA 感知的,因此他们试图减少任务从一个单元到另一个单元的迁移。有时可以在同一个插槽中从一个核心移动到另一个核心;有时会有一个完整的层次结构指定哪些资源(1-、2-、3 级缓存、RAM 通道、IO 等)是共享的,哪些不是,并且通过移动任务来确定是否会受到惩罚. 有时它可以确定等待正确的核心是没有意义的,最好将整个东西铲到另一个插槽....
在绝大多数情况下,最好让调度程序做它最了解的事情。如果没有,你可以玩弄numactl
.
至于给定程序的具体情况;最好的架构很大程度上取决于线程之间的资源共享水平。如果每个线程都有自己的 Playground 并且大部分时间都在其中单独工作,那么足够聪明的分配器会优先考虑本地 RAM,从而降低每个线程碰巧在哪个单元上的重要性。
另一方面,如果对象由一个线程分配,由另一个线程处理并由第三个线程使用;如果它们不在同一个单元格上,性能会受到影响。您可以尝试创建小型线程组并限制组内的大量共享,然后每个组可以毫无问题地进入不同的单元格。
最坏的情况是所有线程都参与到数据共享的大狂欢中。即使您的所有锁和进程都已调试好,也没有任何方法可以优化它以使用比单元上可用的内核更多的内核。甚至最好将整个过程限制为仅使用单个单元中的内核,从而有效地浪费其余部分。
现代的1,我的意思是任何 AMD-64 位芯片,对于英特尔来说,Nehalem 或更好。
2 AMD将此通道称为HyperTransport,Intel名称为QuickPath Interconnect
编辑:
您提到您初始化了“一大块只读内存”。然后产生很多线程来处理它。如果每个线程都在该块的自己的部分上工作,那么在生成它之后在线程上初始化它会好得多。这将允许线程分布到多个内核,分配器将为每个内核选择本地 RAM,这是一种更有效的布局。也许有一些方法可以提示调度程序在它们产生后立即迁移线程,但我不知道细节。
编辑2:
mmap
如果您的数据是从磁盘逐字读取的,没有任何处理,那么使用而不是分配一个大块和read()
ing可能是有利的。有一些共同的优点:
- 无需预先分配 RAM。
- 该
mmap
操作几乎是瞬时的,您可以开始使用它。数据将根据需要延迟读取。
mmap
在应用程序、 ed RAM、缓冲区和缓存之间进行选择时,操作系统可能比您更聪明。
- 它的代码更少!
- 不需要的数据不会被读取,不会占用 RAM。
- 您可以专门标记为只读。任何尝试写入的错误都会导致核心转储。
- 由于操作系统知道它是只读的,它不能是“脏”的,所以如果需要 RAM,它会简单地丢弃它,并在需要时重新读取。
但在这种情况下,您还会得到:
- 由于数据是延迟读取的,因此每个 RAM 页面将在线程分布在所有可用内核上之后被选择;这将允许操作系统选择接近进程的页面。
所以,我认为如果两个条件成立:
- 数据不会在磁盘和 RAM 之间以任何方式处理
- 数据的每一部分(大部分)由一个线程读取,而不是被所有线程读取。
然后,只需使用mmap
,您就应该能够利用任何大小的机器。
如果数据的每一部分都由一个以上的线程读取,也许您可以确定哪些线程将(大部分)共享相同的页面,并尝试提示调度程序将它们保存在同一个 NUMA 单元中。