我们什么时候应该使用 `CacheLinePad` 来避免虚假共享?

When shoud we use `CacheLinePad` to avoid false sharing?

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

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

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

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

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

But for what scene, we should add a pad like the following to improve performance? Are there some rules of thumb here?

规则是:先测量(基准)。然后,如果在某个地方花费了大量时间,请弄清楚 为什么 .

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

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

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

例如,假设我们有一个数据结构,其中有三个(或可能七个)八字节字段,在多 CPU 机器中的每个 CPU 都会读取这些字段,最后一个八字节字段,其中一个 CPU(但只有一个)可能会更新。进一步假设这台机器上的缓存行大小是 32(或者可能是 64)字节,并且 CPU 本身使用类似 MESI or MOESI cache model 的内容。在这种情况下, 写入一个八字节字段的 CPU 会立即使所有其他 CPU 缓存中存在的任何共享副本失效。

但是,如果将由一个 CPU 写入的特定八字节字段在 它自己的 缓存行中,或者至少不在shared 缓存行——例如,在一个单独的数组中——然后写入 CPU 确实 not 使任何共享副本无效;这些在所有 CPU 中都处于 S(共享)状态。

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

但总是先测量!


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