并行代码可扩展性差

时间:2012-10-11 18:21:36

标签: c# .net multithreading scalability task-parallel-library

最近,我一直在分析我的并行计算如何实际加速16核处理器。我得出的一般公式 - 你得到的每个核心速度越慢的线程就越让我感到尴尬。以下是我的CPU负载和处理速度的图表:

picture1

因此,您可以看到处理器负载增加,但速度增加得慢得多。我想知道为什么会发生这样的影响,以及如何得到不可伸缩行为的原因。 我确保使用服务器GC模式。 一旦代码只做

,我就确保我能够并行化适当的代码。
  • 从RAM加载数据(服务器有96 GB的RAM,交换文件不应该被点击)
  • 执行不复杂的计算
  • 在RAM中存储数据

我小心地分析我的应用程序并发现没有瓶颈 - 看起来每个操作都会随着线程数量的增长而变慢。

我被困,我的情景出了什么问题?

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

5 个答案:

答案 0 :(得分:10)

你总会得到这种曲线,它被称为Amdahl's law 问题是它会在多久后保持平稳。

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

答案 1 :(得分:5)

线性可扩展性的关键 - 在从一到两个内核的吞吐量增加一倍的情况下 - 尽可能少地使用共享资源。这意味着:

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

如果你走得这么远,你可能也会对你的代码进行分析和手动调整。

线程池是一种妥协,不适用于不妥协的高性能应用程序。总线程控制是。

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

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

__ _ __ _ _ 修改 <强> _ __ _ __ _

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

答案 2 :(得分:3)

线索越多,回报率越低,可能表明某种瓶颈。

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

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

如果是超线程,你要么做得太多,以致超线程不能使核心性能翻倍,要么核心的内存管道无法处理数据的两倍。

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

由于您添加的线程经常查询数据,这表示存在很大的等待风险。

有没有办法让线程每次都获得更多数据?喜欢阅读10个项目而不是一个?

答案 3 :(得分:1)

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

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

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

答案 4 :(得分:1)

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

这可能会给你带来性能提升,但很可能不会让你达到16倍 - 有很多事情发生在幕后,你只需要逐个淘汰它们。实际上并不是因为你的算法在多线程时以30%的速度运行;更多的是你的单线程算法以300%的速度运行,由各种CPU启用,并且运行多线程的缓存非常难以利用。所以没有什么可以“尴尬”的。 但是尽职尽责,你可以让多线程版本以近300%的速度运行。

另外,如果你把超线程核心算作真正的核心,那么它们就不是。它们只允许线程在被阻塞时非常快速地交换。但他们永远不会让你以双倍的速度跑,除非你的线程在一半的时间内被阻止,在这种情况下,这已经意味着你有机会加速。