我们什么时候应该使用 CacheLinePad 来避免错误共享?

wym*_*mli 3 caching go false-sharing

众所周知,使用 pad 使结构独占一个或多个高速缓存行有利于性能。

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

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

var S struct {
    _                   cpu.CacheLinePad
    A                   string
    _                   cpu.CacheLinePad
}
Run Code Online (Sandbox Code Playgroud)

tor*_*rek 5

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

\n
\n

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

\n
\n

规则是:首先测量(基准)。然后,如果在某个地方花费了大量时间,请找出原因

\n

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

\n

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

\n

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

\n

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

\n

但是,如果将由一个 CPU 写入的特定八字节字段位于其自己的缓存行中,或者至少不在共享缓存行\xe2\x80\x94 中,例如位于单独的数组中\xe2\x80\x94则写入CPU不会使任何共享副本失效;这些在所有 CPU 中都保持在 S(共享)状态。

\n

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

\n

但一定要先测量!

\n
\n

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

\n