我正在开发一个高度并发的 C 程序,当内核数少于 8 时它可以很好地扩展,但拒绝扩展到超过 8 个内核。
我怀疑内存带宽是瓶颈,我如何验证这是否属实?
是否有任何工具/技术/操作系统功能可以帮助诊断?
我正在开发一个高度并发的 C 程序,当内核数少于 8 时它可以很好地扩展,但拒绝扩展到超过 8 个内核。
我怀疑内存带宽是瓶颈,我如何验证这是否属实?
是否有任何工具/技术/操作系统功能可以帮助诊断?
我自己在 NUMA 96x8 内核机器上遇到过这个问题。
90% 的问题在于内存/缓存同步。如果您频繁调用同步例程(原子、互斥锁),则必须使所有套接字上的相应缓存行无效,从而导致整个内存总线完全锁定多个周期。
您可以通过运行像Intel VTune或Perfsuite 之类的分析器对此进行分析,并让它们记录您的原子操作所花费的时间。如果您正确使用它们,那么它们应该需要 10-40 个周期。将我的多线程应用程序扩展到 8 个插槽(Intel Xeon 上的 8x8 内核)时,我遇到的最坏情况是 300 个周期。
您可以做的另一个简单的分析步骤是在没有任何原子/互斥体的情况下编译(如果您的代码允许的话)然后在多个套接字上运行它 - 它应该运行得很快(不正确,但很快)。
您的代码在 8 核上运行速度快的原因是因为英特尔处理器在执行原子操作时使用缓存锁定,只要您将所有代码都保存在同一个物理芯片(套接字)上。如果锁必须进入内存总线 - 这就是事情变得丑陋的时候。
我唯一能建议的是:缩减调用原子/同步例程的频率。
至于我的应用程序:我必须实现一个几乎无锁的数据结构,以便将我的代码扩展到一个套接字之外。每个线程都会累积需要锁定的操作并定期检查,轮到他刷新它们了。然后传递一个令牌并轮流刷新同步操作。显然,只有在等待时您有足够的工作要做时才有效。
虽然获得有关算法和平台的更多信息会有所帮助,但通常有许多原因导致应用程序无法扩展:
使用显式同步(互斥体/原子/事务等):并行程序中的同步意味着当您必须在多个线程之间共享资源时创建一些顺序部分。想要访问临界区的线程越多(原子操作实际上是一个非常小的临界区),您拥有的争用就越多,您的可伸缩性就越有限,因为核心轮流进入临界区。如果无法私有化资源,则减少关键部分的大小并选择不同的数据结构/算法可以缓解这种情况。
错误共享:两个或多个线程共享恰好位于同一缓存块中的不相关对象。当您将应用程序从一个核心扩展到更多以及从一个套接字扩展到一个以上的套接字时,通常可以通过看到增加的缓存未命中率来轻松检测到这一点。将数据结构与缓存块大小对齐通常可以解决这个问题。另请参阅消除虚假共享 - Dobb 博士的
内存分配/释放:虽然内存分配会给你不同的内存块来处理,但你可能会在分配甚至释放时发生争用。可以通过使用可扩展的线程安全内存分配器来解决,例如英特尔 TBB 的可扩展分配器、Hoard等。
空闲线程:您的算法是否具有生产者/消费者模式,您的消费速度是否比生产速度快?您的数据大小是否足够大,可以分摊并行化成本,并且不会因失去局部性而失去速度?由于任何其他原因,您的算法本质上是不可扩展的吗?您可能需要告诉我们更多有关您的平台和算法的信息。Intel Advisor是一个不错的工具,可以检查什么是最好的并行化方式。
并行框架:你在用什么?OpenMP、英特尔 TBB 还是其他?纯线程?您是否可能分叉/加入太多或过度划分您的问题?您的运行时本身是否可扩展?
其他技术原因:线程到内核的错误绑定(可能多个线程最终在同一个内核上),并行运行时的特性(英特尔的 OpenMP 运行时有一个额外的隐藏线程,进行线程到内核绑定可以将这个额外的线程映射到同一个内核上)核心作为主线程,毁了你的一天)等。
根据我的经验,我发现一旦消除了上述所有因素,您就可以开始怀疑内存带宽了。您可以使用STREAM轻松检查它,它可以告诉您内存带宽是否是限制因素。英特尔网站上有这篇文章解释了如何检测内存带宽饱和度。
如果以上都不是结论性的,那么您实际上可能因一致性协议流量和/或 NUMA(非统一内存访问,acmqueue中的一篇好文章)而限制了可扩展性。每当您访问内存中的某些对象时,您要么生成缓存失效请求(您正在共享某些内容并且缓存一致性协议启动),要么您正在访问位于靠近另一个套接字的存储库中的内存(您正在通过处理器互连)。
+1 好问题。
首先我想说还有其他一些因素需要考虑,例如缓存同步,或者像原子内存操作这样不可避免的序列化部分,它们也可能是瓶颈,并且比内存带宽更容易验证。
至于内存带宽,我现在有一个天真的想法,即通过简单地重复访问主内存(一定要考虑缓存的存在)来启动一个简单的守护进程来在分析应用程序时消耗内存带宽。使用守护程序,您可以调整和记录它消耗的内存带宽,并将此结果与应用程序的性能进行比较。
很抱歉提供这么草率的答案..虽然它是可行的 XD
已编辑:另请参阅如何测量当前在 Linux 上使用的内存带宽?以及如何观察内存带宽?