10

我刚刚找到了这个提供无锁环的库,它的工作速度比频道快:https ://github.com/textnode/gringo (而且它的工作速度非常快,尤其是使用 GOMAXPROCS > 1 )

但有趣的部分是用于管理队列状态的结构:

type Gringo struct {
    padding1 [8]uint64
    lastCommittedIndex uint64
    padding2 [8]uint64
    nextFreeIndex uint64
    padding3 [8]uint64
    readerIndex uint64
    padding4 [8]uint64
    contents [queueSize]Payload
    padding5 [8]uint64
}

如果我删除“paddingX [8]uint64”字段,它的工作速度会慢 20%。怎么可能?

如果有人解释为什么这种无锁算法比通道快得多,甚至是缓冲的,也很感激?

4

2 回答 2

13

填充通过将每个结构放在自己的缓存行上来消除错误共享。如果两个变量共享一个缓存行,则读取未修改变量的代价将与读取已修改变量的代价相同,前提是中间存在对另一个变量的写入。

当一个变量在多个内核上被读取并且没有被修改时,缓存线由内核共享。这使得读取非常便宜。在任何核心可以写入该高速缓存行的任何部分之前,它必须使其他核心上的高速缓存行无效。如果任何核心稍后从该缓存行读取,它将发现缓存行无效并且必须返回共享它。当一个变量被频繁修改而另一个变量被频繁读取时,这会产生痛苦的额外缓存一致性流量。

于 2013-10-16T07:44:09.253 回答
4

它工作得更快,因为它不需要锁。是 Java 中的一个实现(称为 Disruptor),效果非常好,似乎是 gringo 的灵感来源。他们在这里解释了锁的成本以及如何提高吞吐量。

至于填充,论文也暗示了一些原因。基本上:处理器缓存。这篇论文很好地解释了它。通过让处理器访问其 1 级缓存而不是尽可能频繁地访问内存或其外部缓存,您可以获得巨大的性能提升。但这需要采取额外的预防措施,因为处理器将完全加载其缓存,并在每次需要时重新加载(从内存或 2-3 级缓存)。在并发数据结构的情况下,正如@David Schwartz 所说,错误共享将迫使处理器更频繁地重新加载其缓存,因为某些数据可能会加载到内存线的其余部分,被修改,并强制整个缓存重新加载。

于 2013-10-16T07:50:07.057 回答