如何检查内存带宽是否成为瓶颈?

44

我正在开发一个高度并发的 C 语言程序,当核心数少于8时,它可以很好地扩展,但在超过8个核心时无法扩展。

我怀疑内存带宽是瓶颈,如何验证这一点?

是否有任何工具/技术/操作系统功能可以帮助诊断?


1
取决于您所说的“内存带宽”和“瓶颈”的含义。通常情况下,如果磁盘不是瓶颈,那么内存访问就是瓶颈,除了某些高度计算密集型任务。但是,特别是在运行多处理器时,瓶颈很少是RAM速度本身,而是缓存争用及相关问题。 - Hot Licks
@HotLicks,感谢您的建议。我同意有许多问题可能导致可扩展性受限,但在这里我想确定并消除其中一个——内存带宽。 - user972946
如果在超过8时出现问题,几乎可以肯定是缓存受限。 - Hot Licks
3个回答

15

我在一台NUMA 96x8核心的机器上也遇到过这个问题。

90%的情况是由于内存/缓存同步出现了问题。如果您频繁调用同步程序(原子操作、互斥量),则所有套接字上的适当缓存行都必须无效化,导致整个内存总线被完全锁定多个周期。

您可以通过运行像Intel VTunePerfsuite这样的性能分析器来分析此问题,并记录您的原子操作所需的时间。如果使用正确,则应该需要大约10-40个周期。最坏的情况是,在将我的多线程应用程序扩展到8个套接字(Intel Xeon上的8x8核心)时,我的原子操作需要300个周期。

另一个简单的分析步骤是,如果您的代码允许,请在没有任何原子/互斥量的情况下进行编译,然后在多个套接字上运行它-它应该运行得很快(不正确,但快速)。

您的代码之所以在8个核心上运行得很快,是因为英特尔处理器在执行原子操作时使用缓存锁定,只要您将它们全部保持在同一物理芯片(套接字)上。如果锁定必须转到内存总线,则情况就会变得糟糕。

我唯一能建议的是:减少调用原子操作/同步程序的频率。

至于我的应用程序:我不得不实现几乎无锁数据结构,以便将代码扩展到一个以上的套接字。每个线程积累需要锁定的操作,并定期检查是否轮到他刷新它们。然后传递令牌,轮流刷新同步操作。显然,只有在等待时有足够的工作可做时才有效。


2
尽管了解算法和平台的更多信息会很有帮助,但通常应用程序不具备可扩展性的原因有以下几点:
  1. 显式同步使用(互斥锁/原子操作/事务等):并行程序中的同步意味着在多个线程之间共享资源时创建一些顺序部分。想要访问关键部分的线程越多(原子操作实际上是一个非常小的关键部分),争用就越大,可扩展性就越受限制,因为核心正在轮流进入关键部分。如果无法将资源私有化,则减少关键部分的大小并选择不同的数据结构/算法可以缓解这种情况。
  2. 虚假共享:两个或多个线程共享不相关的对象,这些对象恰好位于相同的缓存块中。通过从一个核心扩展应用程序到多个核心以及从一个套接字扩展到多个套接字,通常很容易检测到增加的缓存未命中。将数据结构与缓存块大小对齐通常可以解决该问题。另请参见 Eliminate False Sharing - Dr Dobb's
  3. 内存分配/释放:虽然内存分配将为您提供不同的内存块供多个线程使用,但您可能会在分配或释放时遇到争用。可以通过使用可扩展的线程安全内存分配器(例如 Intel TBB's scalable allocatorHoard 等)来解决该问题。
  4. 空闲线程:您的算法是否具有生产者/消费者模式,您是否比生产更快地消耗资源?您的数据大小足够大,以便将并行化成本摊销,并且不会因失去局部性而失去速度吗?您的算法因其他原因本质上无法扩展吗?您可能需要告诉我们更多关于您的平台和算法的信息。 Intel Advisor 是一个检查最佳并行化方式的不错工具。
  5. 并行框架:您在使用什么?OpenMP、Intel TBB 还是其他东西?纯线程?您是否过度分叉/合并问题或过度分区问题?您的运行时本身是否可扩展?
  6. 其他技术原因:线程与核心的不正确绑定(可能会导致多个线程最终位于同一核心上)、并行运行时的特性(Intel 的 OpenMP 运行时具有一个额外的隐藏线程,进行线程到核心的绑定可以将此附加线程映射到与主线程相同的核心上,从而破坏您的计划)等。
从我的经验来看,如果你已经排除了以上所有可能的问题,那么你可以怀疑内存带宽。你可以使用STREAM轻松检查内存带宽是否是限制因素。在Intel网站上有一篇文章,介绍了如何检测内存带宽饱和。
如果以上都不确定,你可能会受到一些缓存一致性流量和/或NUMA(非统一内存访问)的限制(acmqueue中有一篇好文章)。每当你访问内存中的某个对象时,你要么生成缓存失效请求(你正在共享某些内容,缓存一致性协议开始工作),要么访问的内存位于更靠近另一个插槽的银行中(你正在通过处理器互连进行操作)。

2

+1 个好问题。

首先,我想说还有其他因素需要考虑,例如缓存同步或不可避免的序列化部分,如原子内存操作,这些也可能成为瓶颈,比内存带宽更容易验证。

至于内存带宽,我现在拥有的是一个天真的想法,就是启动一个简单的守护进程来消耗内存带宽,同时对您的应用程序进行分析,通过简单地重复访问主内存(请务必考虑缓存的存在)。使用该守护进程,您可以调整和记录其所消耗的内存带宽,并将此结果与您的应用程序性能进行比较。

很抱歉提供了如此粗糙的答案...虽然这是可行的 XD

编辑:另请参见如何测量当前在 Linux 上使用的内存带宽?如何观察内存带宽?


2
@HowardGuo 谢谢。在第二个链接中,我介绍的性能分析器似乎很有前途!作者还发布了几个自己的结果。然而,其源代码的下载链接已经失效了>< - starrify

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接