猿问

为什么带有填充字段的结构工作得更快

我刚刚找到了这个库,它提供了无锁环,它的运行速度比通道快: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%。怎么可能?


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


慕勒3428872
浏览 157回答 2
2回答

jeck猫

填充通过将每个结构放在自己的缓存行上来消除错误共享。如果两个变量共享一个缓存行,如果有对另一个变量的干预写入,则读取未修改的变量将与读取修改的变量一样昂贵。当一个变量在多个内核上被读取而不被修改时,缓存行由内核共享。这使得读取非常便宜。在任何内核可以写入该缓存行的任何部分之前,它必须使其他内核上的缓存行无效。如果任何内核稍后从该缓存行读取,它将发现缓存行无效并且必须返回共享它。当一个变量被频繁修改而另一个变量被频繁读取时,这会产生痛苦的额外缓存一致性流量。

繁星淼淼

它运行得更快,因为它不需要锁。这是 Java 中的一个实现(称为 Disruptor),效果非常好,似乎是 gringo 的灵感来源。他们解释锁的成本,以及如何可以增加吞吐量这里。至于padding,论文也暗示了一些原因。基本上:处理器缓存。这篇论文很好地解释了它。通过让处理器访问其 1 级缓存而不是尽可能频繁地访问内存或其外部缓存,您可以获得巨大的性能提升。但这需要采取额外的预防措施,因为处理器将完全加载其缓存,并在每次需要时重新加载(从内存或 2-3 级缓存)。在并发数据结构的情况下,正如@David Schwartz 所说,错误共享将迫使处理器更频繁地重新加载其缓存,因为一些数据可能会加载到内存行的其余部分中,被修改,并强制整个缓存再次加载。
随时随地看视频慕课网APP

相关分类

Go
我要回答