在PThreads的上下文中的多处理器与多线程

时间:2012-10-04 18:37:51

标签: c multithreading pthreads hardware multiprocessing

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

我在多核单CPU盒上测试了多线程代码。

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

  • 一个6核单CPU盒
  • 4核双CPU盒

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

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

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

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

3 个答案:

答案 0 :(得分:8)

现代CPU 1 在本地处理RAM并使用单独的通道 2 在它们之间进行通信。这是十多年前为超级计算机创建的NUMA架构的消费级版本。

这个想法是避免可能导致严重争用的共享总线(旧FSB),因为每个核心都使用它来访问内存。随着您添加更多NUMA单元,您可以获得更高的带宽。缺点是从CPU的角度来看内存变得不均匀:某些RAM比其他内存更快。

当然,现代操作系统调度程序具有NUMA感知功能,因此他们尝试减少任务从一个单元格迁移到另一个单元格。有时可以在同一个插槽中从一个核心移动到另一个核心;有时会有一个完整的层次结构,指定哪些资源(一,二,三级缓存,RAM通道,IO等)是共享的,哪些不是,并且通过移动任务来确定是否会有惩罚。有时它可以确定等待正确的核心是没有意义的,最好把整个东西铲到另一个插座上......

在绝大多数情况下,最好让调度程序执行它最熟悉的操作。如果没有,您可以使用numactl

至于给定程序的具体情况;最好的体系结构在很大程度上取决于线程之间的资源共享级别。如果每个线程都有自己的游乐场并且大部分都在其中运行,那么足够聪明的分配器会优先考虑本地RAM,使得每个线程碰巧在哪个单元格上变得不那么重要。

另一方面,如果对象由一个线程分配,由另一个线程分配并由第三个线程消耗;如果他们不在同一个小区,性能会受到影响。您可以尝试创建小线程组并限制组内的大量共享,然后每个组可以在没有问题的情况下进入不同的单元格。

最糟糕的情况是所有线程都参与了大量的数据共享。即使您已经对所有锁和流程进行了很好的调试,也没有任何方法可以优化它以使用比单元上可用的内核更多的内核。甚至最好将整个过程限制在单个单元中使用核心,有效地浪费其余部分。

现代的

1 ,我的意思是任何AMD-64bit芯片,Nehalem或更好的英特尔。

2 AMD将此频道称为HyperTransport,英特尔名称为QuickPath Interconnect

修改

你提到你初始化“一大块只读内存”。然后产生很多线程来处理它。如果每个线程都在它自己的那个块上工作,那么如果你在产生它之后在线程上初始化它会更好。这将允许线程扩展到多个核心,分配器将为每个核心选择本地RAM,这是一种更有效的布局。也许有一些方法可以提示调度程序在生成线程后立即迁移它们,但我不知道细节。

编辑2:

如果您的数据是从磁盘逐字读取的,而不进行任何处理,则使用mmap而不是分配大块和read()可能是有利的。有一些共同的优点:

  1. 无需预先分配RAM。
  2. mmap操作几乎是即时的,您可以开始使用它。根据需要,数据将被懒惰地读取。
  3. 在应用程序,mmap内存,缓冲区和缓存之间进行选择时,操作系统可能比您更聪明。
  4. 代码少了!
  5. 不会读取不需要的数据,不会耗尽RAM。
  6. 您可以专门标记为只读。尝试编写的任何错误都会导致coredump。
  7. 由于操作系统知道它是只读的,所以它不能“脏”,因此如果需要RAM,它只会丢弃它,并在需要时重新读取。
  8. 但在这种情况下,你也得到:

    • 由于数据是懒惰读取的,因此线程在所有可用内核上传播后,将选择每个RAM页面;这将允许操作系统选择接近该过程的页面。

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

    • 磁盘和RAM之间不以任何方式处理数据
    • 数据的每个部分(大部分)由一个单独的线程读取,而不是全部触及。

    然后,只需使用mmap,您就可以利用任何规模的机器。

    如果数据的每个部分都被多个单个线程读取,则可能您可以识别哪些线程(大多数)将共享相同的页面,并尝试提示调度程序将这些线程保存在同一个NUMA单元中。

答案 1 :(得分:2)

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

然而,表演是另一回事。对于跨套接字内存访问存在速度损失,因此未修改的程序可能无法充分发挥其潜力。

不幸的是,很难提前说明您的代码是否会在6核单节点盒或8核双节点盒上运行得更快。即使我们能看到你的代码,它最终也会成为一种有根据的猜测。需要考虑的一些事项:

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

存在很多变量,因此最好的办法是向HP经销商咨询与您正在考虑的配置相匹配的借用设备。然后,您可以测试您的应用程序,查看它的最佳性能并相应地订购硬件。

答案 2 :(得分:1)

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

如果您的线程代码正确(例如,您正确锁定了共享资源),则不应遇到因更改硬件体系结构而引入的任何错误。不正确的线程代码有时会被特定平台处理CPU缓存访问/共享等内容的细节所掩盖。

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

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

此类缓存行争用称为 False Sharing 。我建议以下阅读以了解这可能是您面临的问题

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

最重要的是,如果你遵循正确的线程开发实践,应用程序行为应该是稳定的(除了自然依赖于线程调度细节的事情),但性能可能取决于你正在做什么。