9

我们刚刚购买了一台 32 核 Opteron 机器,我们得到的加速有点令人失望:超过 24 个线程,我们根本看不到加速(实际上总体上变慢了),大约 6 个线程后,它变得明显亚线性。

我们的应用程序对线程非常友好:我们的工作分解为大约 170,000 个小任务,每个小任务都可以单独执行,每个任务需要 5-10 秒。它们都从同一个大小约为 4Gb 的内存映射文件中读取。他们偶尔会对其进行写入,但每次写入可能需要 10,000 次读取 - 我们只是在 170,000 个任务的每一个结束时写入一点数据。写入受锁定保护。分析表明锁不是问题。线程在非共享对象中使用大量 JVM 内存,它们对共享 JVM 对象的访问很少,其中只有一小部分访问涉及写入。

我们在启用了 NUMA 的 Linux 上使用 Java 进行编程。我们有 128Gb 内存。我们有 2 个 Opteron CPU(型号 6274),每个 CPU 有 16 个内核。每个 CPU 有 2 个 NUMA 节点。在英特尔四核(即 8 核)上运行的相同作业几乎可以线性扩展到 8 个线程。

我们尝试将只读数据复制到每个线程一个,希望大多数查找可以在 NUMA 节点本地进行,但我们观察到这并没有加速。

对于 32 个线程,“top”显示 CPU 的 74%“us”(用户)和大约 23%“id”(空闲)。但是没有睡眠,几乎没有磁盘 i/o。使用 24 个线程,我们得到 83% 的 CPU 使用率。我不确定如何解释“空闲”状态 - 这是否意味着“等待内存控制器”?

我们尝试打开和关闭 NUMA(我指的是需要重新启动的 Linux 级别设置)并且没有发现任何区别。启用 NUMA 时,“numastat”仅显示大约 5% 的“分配和访问未命中”(95% 的缓存未命中发生在 NUMA 节点本地)。[编辑:] 但是添加“-XX:+useNUMA”作为 java 命令行标志给了我们 10% 的提升。

我们的一个理论是我们正在最大化内存控制器,因为我们的应用程序使用了大量的 RAM,并且我们认为有很多缓存未命中。

我们可以做些什么来(a)加速我们的程序以接近线性可伸缩性,或者(b)诊断正在发生的事情?

另外:(c)我如何解释“顶部”结果-“空闲”是否意味着“在内存控制器上被阻塞”?(d) Opteron 与 Xeon 的特性有什么不同吗?

4

5 回答 5

3

我还有一台 32 核 Opteron 机器,有 8 个 NUMA 节点(4x6128 处理器,Mangy Cours,不是 Bulldozer),我也遇到过类似的问题。

我认为顶部显示的 2.3%“sys”时间暗示了您的问题的答案。根据我的经验,这个 sys 时间是系统在内核中等待锁定的时间。当一个线程无法获得锁时,它就会处于空闲状态,直到它进行下一次尝试。sys 时间和空闲时间都是锁争用的直接结果。你说你的分析器没有显示锁是问题所在。我的猜测是,由于某种原因,导致问题锁定的代码未包含在配置文件结果中。

在我的情况下,锁争用的一个重要原因不是我实际正在执行的处理,而是将单个工作分配给每个线程的工作调度程序。此代码使用锁来跟踪哪个线程正在执行哪项工作。我对这个问题的解决方案是重写我的工作调度程序以避免互斥体,我已经读过它不能很好地扩展到 8-12 个内核之外,而是使用 gcc 内置原子(我在 Linux 上用 C 编程)。原子操作实际上是一种非常细粒度的锁,可以在高核心数的情况下更好地扩展。在您的情况下,如果您的工作包裹确实需要 5-10 秒,那么这对您来说似乎不太可能。

我也遇到了 malloc 的问题,它在高核心数的情况下会遇到可怕的锁定问题,但是我不知道这是否也导致了顶部的 sys 和空闲数字,或者它是否只是出现了使用 Mike Dunlavey 的调试器分析方法(如何分析在 Linux 中运行的 C++ 代码?)。我怀疑它确实会导致 sys & idle 问题,但我在挖掘所有旧笔记以找出答案时划清界限:) 我知道我现在尽可能避免运行时 malloc。

我最好的猜测是,您正在使用的某些库代码在您不知情的情况下实现了锁,未包含在您的分析结果中,并且无法很好地扩展到高核心数的情况。当心内存分配器!

于 2013-04-10T18:44:41.697 回答
2

我确信答案将在于对硬件架构的考虑。您必须将多核计算机视为通过网络连接的单个机器。事实上,这就是 Hypertransport 和 QPI 的全部内容。

我发现要解决这些可伸缩性问题,您必须停止考虑共享内存并开始采用通信顺序进程的理念。这意味着思维方式非常不同,即想象一下,如果您的硬件是通过网络连接的 32 台单核机器,您将如何编写软件。现代(和古老)的 CPU 架构并非旨在提供您所追求的那种不受限制的扩展。它们旨在允许许多不同的进程继续处理自己的数据。

就像计算中的其他一切一样,这些东西也很流行。CSP 可以追溯到 1970 年代,但非常现代的 Java 派生的 Scala 是该概念的流行体现。请参阅Wikipedia 上有关 Scala 并发的这一部分。

CSP 的理念是迫使您设计适合您的数据和您正在解决的问题的数据分发方案。这并不一定容易,但如果你管理它,那么你就有一个确实可以很好扩展的解决方案。Scala 可能会使开发变得更容易。

就我个人而言,我在 CSP 和 C 中做所有事情。它使我能够开发一个信号处理应用程序,该应用程序可以从 8 个内核完美线性扩展到几千个内核(限制是我的房间有多大)。

您要做的第一件事就是实际使用 NUMA。你打开它并不是一个神奇的设置,你必须在你的软件架构中利用它。我不了解 Java,但在 C 中,将内存分配绑定到特定核心的内存控制器(也称为内存关联),并且在操作系统没有得到提示的情况下,线程也类似(核心关联)。

我认为您的数据不会分解为 32 个整齐的离散块?如果不确切了解程序中隐含的数据流,就很难给出建议。但是从数据流的角度考虑。把它画出来;数据流图对此很有用(另一种古老的图形形式符号)。如果您的图片显示您的所有数据都通过单个对象(例如通过单个内存缓冲区),那么它会很慢......

于 2013-04-10T21:50:04.937 回答
1

我假设你已经优化了你的锁,并且同步做了最小化。在这种情况下,它仍然很大程度上取决于您用于并行编程的库。

即使您没有同步问题,也可能发生的一个问题是内存总线拥塞。这是非常讨厌且难以摆脱的。我只能建议以某种方式使您的任务更大并创建更少的任务。这在很大程度上取决于您的问题的性质。理想情况下,您需要与内核/线程数一样多的任务,但这并不容易(如果可能)实现。

其他可以提供帮助的方法是为您的 JVM 提供更多堆。这将减少频繁运行垃圾收集器的需要,并加快一点速度。

“空闲”是否意味着“在内存控制器上被阻塞”

不,你没有在顶部看到它。我的意思是如果 CPU 正在等待内存访问,它将显示为忙。如果你有空闲时间,它要么在等待锁,要么在等待 IO。

于 2012-11-30T13:26:50.563 回答
1

我是原始海报。我们认为我们已经诊断出了问题,不是锁,不是系统调用,也不是内存总线拥塞;我们认为这是2/3 级 CPU 缓存争用

重申一下,我们的任务是令人尴尬的并行,因此它应该可以很好地扩展。然而,一个线程拥有大量可以访问的 CPU 缓存,但是随着我们添加更多线程,每个进程可以访问的 CPU 缓存量越来越低(相同数量的缓存除以更多进程)。某些架构上的某些级别在裸片上的内核之间共享,有些甚至在裸片之间共享(我认为),这可能有助于使用您正在使用的特定机器“摆脱困境”,并优化您的算法,但我们的结论是,要实现我们认为可以获得的可扩展性,我们无能为力。

我们通过使用 2 种不同的算法将其确定为原因。访问更多 2/3 级缓存的缓存比使用更少数据进行更多处理的缓存更差。它们都频繁访问主存中的主要数据。

于 2019-04-07T23:55:10.477 回答
0

如果您还没有尝试过:查看像 Oracle Studio 那样的硬件级分析器(用于 CentOS、Redhat 和 Oracle Linux),或者如果您被 Windows 卡住了:Intel VTune。然后开始查看每个指令指标具有可疑高时钟的操作。可疑的高意味着比单 numa、单 L3 缓存机器(如当前的英特尔台式机 CPU)上的相同代码高很多。

于 2019-04-06T17:58:24.970 回答