并行代码可扩展性差

Kon*_*nov 5 .net c# multithreading scalability task-parallel-library

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

图片1

因此,您可以看到处理器负载增加,但速度增加得慢得多.我想知道为什么会发生这样的影响,以及如何得到不可伸缩行为的原因.我已确保使用服务器GC模式.我已经确保,只要代码不做任何事情,我就会并行化适当的代码

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

我仔细分析了我的应用程序并发现没有瓶颈 - 看起来随着线程数量的增加,每个操作变慢.

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

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

Hen*_*man 10

你将永远得到这种曲线,它被称为Amdahl定律.
问题是它会在多长时间内保持平稳.

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


Olo*_*ell 5

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

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

如果你已经走到这一步,你可能也会对你的代码进行分析和手动调整.

线程池是一种折衷方案,不适用于不妥协的高性能应用程序.总线程控制是.

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

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

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

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