5

最近我一直在分析我的并行计算是如何在 16 核处理器上实际加速的。我得出的一般公式——线程越多,每个核心的速度越慢——让我很尴尬。这是我的cpu负载和处理速度的图表:

图片1

因此,您可以看到处理器负载增加,但速度增加得慢得多。我想知道为什么会发生这种影响以及如何获得不可扩展行为的原因。我确保使用Server GC mode。我已经确保我正在并行化适当的代码,只要代码只做

  • 从 RAM 加载数据(服务器有 96 GB 的 RAM,不应命中交换文件)
  • 执行不复杂的计算
  • 将数据存储在 RAM 中

我仔细分析了我的应用程序并没有发现任何瓶颈 - 看起来每个操作都会随着线程数的增加而变慢。

我被卡住了,我的场景有什么问题?

我使用 .Net 4 任务并行库。

4

5 回答 5

10

你总会得到这种曲线,这叫做阿姆达尔定律
问题是它多久会趋于平稳。

您说您检查了代码是否存在瓶颈,让我们假设这是正确的。然后还有内存带宽和其他硬件因素。

于 2012-10-11T18:31:37.860 回答
5

线性可扩展性的关键——在从一个内核到两个内核使吞吐量翻倍的情况下——是尽可能少地使用共享资源。这表示:

  • 不要使用超线程(因为两个线程共享相同的核心资源)
  • 将每个线程绑定到特定的核心(否则操作系统将在核心之间处理线程)
  • 不要使用比内核更多的线程(操作系统将换入和换出)
  • 留在核心自己的缓存中 - 现在的 L1 和 L2 缓存
  • 除非绝对必要,否则不要冒险进入 L3 缓存或 RAM
  • 最小化/节省关键部分/同步使用

如果您已经走到这一步,您可能也已经对您的代码进行了概要分析和手动调整。

线程池是一种折衷方案,不适合不折不扣的高性能应用程序。总线程控制是。

不要担心操作系统调度程序。如果您的应用程序受 CPU 限制,需要进行长时间的计算,主要是进行本地 L1 和 L2 内存访问,那么将每个线程绑定到自己的内核是一个更好的性能选择。当然操作系统会进来,但与你的线程正在执行的工作相比,操作系统的工作可以忽略不计。

另外我应该说我的线程经验主要来自 Windows NT 引擎机器。

_ _ __ _ _编辑_ __ _ __ _

并非所有内存访问都与数据读取和写入有关(请参阅上面的评论)。一个经常被忽视的内存访问是获取要执行的代码。所以我关于留在核心自己的缓存中的声明意味着确保所有必要的数据和代码都驻留在这些缓存中。还要记住,即使是非常简单的 OO 代码也可能会生成对库例程的隐藏调用。在这方面(代码生成部门),OO 和解释代码比 C(通常是所见即所得)或程序集(完全所见即所得)要少得多。

于 2012-10-11T22:05:01.817 回答
3

更多线程的回报普遍减少可能表明某种瓶颈。

是否有任何共享资源,例如集合或队列或其他东西,或者您是否使用了一些可能依赖于某些有限资源的外部函数?

8 线程的突然中断很有趣,在我的评论中,我询问 CPU 是真正的 16 核还是具有超线程的 8 核,其中每个内核在操作系统中显示为 2 个内核。

如果是超线程,要么你的工作量太大,以至于超线程无法使核心的性能翻倍,要么通往核心的内存管道无法处理两倍的数据吞吐量。

线程执行的工作是均匀还是某些线程比其他线程做得更多,这也可能表明资源不足。

由于您添加了线程非常频繁地查询数据,这表明等待的风险非常大。

有什么方法可以让线程每次获取更多数据?喜欢阅读 10 项而不是一项?

于 2012-10-11T18:29:57.347 回答
1

您的线程是否正在读取和写入内存中靠近的项目?那么您可能会遇到虚假共享。如果线程 1 使用 data[1] 而线程 2 使用 data[2],那么即使在理想世界中,我们知道线程 2 连续两次读取 data[2] 将始终产生相同的结果,但在现实世界中,如果 thread1 在这两次读取之间的某个时间更新 data[1],则 CPU 会将缓存标记为脏并更新它。http://msdn.microsoft.com/en-us/magazine/cc872851.aspx。要解决这个问题,请确保每个线程正在使用的数据在内存中与其他线程正在使用的数据足够远。

这可能会给你带来性能提升,但可能不会让你达到 16 倍——引擎盖下发生了很多事情,你只需要一个接一个地敲掉它们。实际上,并不是说您的算法在多线程时以 30% 的速度运行;更重要的是,您的单线程算法以 300% 的速度运行,由运行多线程的各种 CPU 和缓存功能支持,而运行多线程则更难利用。所以没有什么可“尴尬”的。 但是通过一些努力,您也许可以让多线程版本以接近 300% 的速度运行。

此外,如果您将超线程内核视为真正的内核,那么它​​们不是。它们只允许线程在一个被阻塞时快速交换。但是它们永远不会让你以双倍速度运行,除非你的线程无论如何都被阻塞了一半,在这种情况下,这已经意味着你有机会加速。

于 2012-10-12T16:24:08.023 回答
1

如果你正在做内存密集型的事情,你可能会达到缓存容量。

您可以使用模拟算法对此进行测试,如果数据一遍又一遍地处理相同的一小部分,因此它们都应该适合缓存。

如果它确实是缓存,可能的解决方案可能是让线程以某种方式处理相同的数据(如小数据窗口的不同部分),或者只是将算法调整为更本地化(如排序,合并排序通常比快速排序慢,但它对缓存更友好,在某些情况下仍然使它更好)。

于 2012-10-11T18:33:51.527 回答