3

众所周知,使用 pad 使结构独占一个或多个缓存行对性能有好处。

但是对于什么场景,我们应该添加如下这样的pad来提高性能呢?
这里有一些经验法则吗?

import "golang.org/x/sys/cpu"

var S struct {
    _                   cpu.CacheLinePad
    A                   string
    _                   cpu.CacheLinePad
}
4

1 回答 1

3

我从来没有真正喜欢过“虚假分享”这个词。我认为将其称为“不当分享”或“过度分享”会更好。

但是对于什么场景,我们应该添加如下这样的pad来提高性能呢?这里有一些经验法则吗?

规则是:先测量(基准)。然后,如果很多时间都花在某个地方,找出原因

如果您使用的底层软件和硬件坚持以缓慢的方式移动数据,即使有更快的方式可用,“虚假共享”也会导致性能问题。通过扭曲您自己的代码,您可以说服软件和/或硬件使用更快的方法。

这样做通常会降低您自己的代码的可读性,或者占用更多空间,或者有一些其他类似的缺点。确保增加速度的价值超过了这个缺点的成本。如果您的软件以相同或更低的速度运行,那么为了速度而损坏代码的成本并没有得到支付,所以不要这样做。1

“错误共享”的常见情况——这就是我不喜欢这个术语的原因——发生在某些数据结构中的某些数据可以很好地共享时,被多个 CPU 使用在多个缓存中,除了某些特定的数据项写入(存储操作)发生这种情况时,一个 CPU 会使所有其他 CPU 的缓存无效,因此所有其他 CPU 必须返回主内存或从写入 CPU 重新复制数据。您描述的“插入填充”技巧在写入 CPU 不再影响其他 CPU 使用相邻数据项时会有所帮助,因为这些项虽然在逻辑上是相邻的(例如,在数组或切片的连续元素中),但没有不再占用一个因写入而失效的高速缓存行。

例如,假设我们有一个数据结构,其中有 3 个(或者可能是 7 个)八字节字段供多 CPU 机器中的每个 CPU 读取,最后一个八字节字段供其中一个 CPU(但只有一个)可能会更新。进一步假设这台机器上的缓存线大小是 32(或者可能是 64)字节,并且 CPU 本身使用类似MESIMOESI 缓存模型的东西。在这种情况下,写入一个八字节字段的一个 CPU 会立即使所有其他 CPU 的缓存中存在的任何共享副本无效。

然而,如果将由一个 CPU 写入的特定 8 字节字段在其自己的高速缓存行中,或者至少不在共享高速缓存行中(例如,在单独的阵列中),则写入 CPU不会使任何共享副本无效;这些在所有 CPU 中都处于 S(共享)状态。

如果编译器可以移动某些数据结构的只读和读/写字段,以便在时间上受益于共享的可共享部分保持可共享,您将无需调整您的自己的代码。Go 与 C 和 C++ 一样,对编译器施加了一些限制,可能会阻止它们在此处进行自己的优化,这意味着您可能必须自己进行。

但总是先测量!


1这类似于股市赚钱的规则:股票要涨就买。如果没有涨,就不要买了。但至少电脑版实际上是可以实现的,因为你可以同时运行你的原始版本和你付出的代价扭曲的版本,看看你付出的代价是否值得。

于 2021-07-14T07:39:15.917 回答