4

我有一个关于硬件选择及其对软件开发的影响的应用程序级别 (PThreads) 问题。

我已经在多核单 CPU 机器上很好地测试了多线程代码。

我正在尝试决定为我的下一台机器购买什么:

  • 一个6核单CPU盒子
  • 4核双CPU机箱

我的问题是,如果我选择双 CPU 机箱,这会严重影响我的代码移植吗?或者我可以分配更多线程并让操作系统处理其余的吗?

换句话说,在 PThreads 应用程序的上下文中,多处理器编程与(单 CPU)多线程有什么不同吗?

我认为在这个级别上没有什么区别,但是在配置一个新机器时,我注意到必须为每个 CPU 购买单独的内存。那时我遇到了一些认知失调。

关于代码的更多细节(对于那些感兴趣的人): 我将大量数据从磁盘读取到一个巨大的内存块中(大约 24GB 很快就会更多),然后我产生了我的线程。最初的内存块是“只读的”(由我自己的代码策略强制执行),因此我不会对该块进行任何锁定。我在看 4 核双 CPU 机箱时感到困惑——它们似乎需要单独的内存。在我的代码上下文中,我不知道如果我分配一堆额外的线程会发生什么“幕后”。操作系统会将我的内存块从一个 CPU 的内存库复制到另一个? 这将影响我必须购买多少内存(提高此配置的成本)。 理想的情况(成本方面和易于编程方面)是让双 CPU 共享一大块内存,但如果我理解正确的话,这在新的英特尔双核 MOBO 上可能是不可能的(比如HP ProLiant ML350e)?

4

3 回答 3

8

现代 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可能是有利的。有一些共同的优点:

  1. 无需预先分配 RAM。
  2. mmap操作几乎是瞬时的,您可以开始使用它。数据将根据需要延迟读取。
  3. mmap在应用程序、 ed RAM、缓冲区和缓存之间进行选择时,操作系统可能比您更聪明。
  4. 它的代码更少!
  5. 不需要的数据不会被读取,不会占用 RAM。
  6. 您可以专门标记为只读。任何尝试写入的错误都会导致核心转储。
  7. 由于操作系统知道它是只读的,它不能是“脏”的,所以如果需要 RAM,它会简单地丢弃它,并在需要时重新读取。

但在这种情况下,您还会得到:

  • 由于数据是延迟读取的,因此每个 RAM 页面将在线程分布在所有可用内核上之后被选择;这将允许操作系统选择接近进程的页面。

所以,我认为如果两个条件成立:

  • 数据不会在磁盘和 RAM 之间以任何方式处理
  • 数据的每一部分(大部分)由一个线程读取,而不是被所有线程读取。

然后,只需使用mmap,您就应该能够利用任何大小的机器。

如果数据的每一部分都由一个以上的线程读取,也许您可​​以确定哪些线程将(大部分)共享相同的页面,并尝试提示调度程序将它们保存在同一个 NUMA 单元中。

于 2012-10-04T19:10:21.977 回答
2

对于您正在查看的 x86 机器,内存物理连接到不同 CPU 插槽的事实是一个实现细节。从逻辑上讲,机器的总内存显示为一个大池 - 您无需更改应用程序代码即可使其在两个 CPU上正确运行。

然而,性能是另一回事。跨套接字内存访问会降低速度,因此未修改的程序可能无法充分发挥其潜力。

不幸的是,很难提前说您的代码是在 6 核单节点机器上还是在 8 核双节点机器上运行得更快。即使我们可以看到您的代码,它最终也将是一个有根据的猜测。有几点需要考虑:

  • 跨套接字内存访问惩罚只会在缓存未命中时起作用,因此如果您的程序具有良好的缓存行为,那么 NUMA 不会对您造成太大伤害;
  • 如果您的线程都在写入私有内存区域并且您受到写入内存带宽的限制,那么双插槽机器将最终提供帮助;
  • 如果您受计算限制而不是内存带宽限制,那么 8 核可能优于 6;
  • 如果您的性能受到缓存读取未命中的限制,那么 6 核单插槽盒开始看起来更好;
  • 如果您有很多锁争用或写入共享数据,那么这又倾向于建议使用单套接字盒。

有很多变数,所以最好的办法是向您的惠普经销商询问与您正在考虑的配置相匹配的借用机器。然后,您可以测试您的应用程序,看看它在哪里表现最好,并相应地订购您的硬件。

于 2012-10-05T04:32:39.947 回答
1

没有更多细节,很难给出详细的答案。但是,希望以下内容可以帮助您解决问题。

如果你的线程代码是正确的(例如你正确地锁定了共享资源),你应该不会遇到任何由硬件架构变化引入的错误。不正确的线程代码有时会被特定平台如何处理 CPU 缓存访问/共享等细节所掩盖。

由于单芯片、多核与多芯片替代方案中的内存和缓存管理方法不同,您可能会遇到每个等效内核的应用程序性能发生变化。

具体来说,如果您正在查看每个 CPU 具有单独内存的硬件,我会假设每个线程都将被锁定到它启动的 CPU(否则,系统将不得不产生大量开销来将线程的内存移动到专用内存到不同的核心)。根据您的具体情况,这可能会降低整体系统效率。但是,每个内核单独的内存也意味着不同的 CPU 不会相互竞争给定的缓存线(每个双 CPU 上的 4 个内核仍可能竞争缓存线,但这比 6 个内核的争用要少正在竞争相同的缓存行)。

这种类型的缓存行争用称为虚假共享。我建议阅读以下内容以了解这是否可能是您面临的问题

http://www.drdobbs.com/parallel/eliminate-false-sharing/217500206?pgno=3

底线是,如果您遵循正确的线程开发实践,应用程序行为应该是稳定的(除了自然依赖于线程调度细节的事情),但性能可能会根据您正在做的事情而有所不同。

于 2012-10-04T18:46:35.360 回答