我刚刚发现这个提供无锁环的库,其工作方式比通道快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%。它怎么样?
还要感谢有人解释为什么这种无锁算法比通道快得多,甚至缓冲?
答案 0 :(得分:11)
Padding通过将每个结构放在自己的缓存行上来消除false sharing。如果两个变量共享一个缓存行,那么如果对另一个变量进行插入写入,则读取未修改的变量将与读取修改后的变量一样昂贵。
当在多个内核上读取变量而未修改变量时,内核将共享缓存行。这使得读取非常便宜。在任何内核可以写入该缓存行的任何部分之前,它必须使其他内核上的缓存行无效。如果任何核心稍后从该缓存行读取,它将发现缓存行无效并且必须返回共享它。当一个变量经常被修改而另一个变量经常被读取时,这会产生痛苦的额外高速缓存一致性流量。
答案 1 :(得分:3)
它的工作速度更快,因为它不需要锁定。 This是Java中的一个实现(称为Disruptor),它非常有效,似乎是gringo的灵感来源。他们解释了锁的成本以及如何提高吞吐量here。
至于填充,该文件还暗示了一些原因。基本上:处理器缓存。 This paper解释得很清楚。通过使处理器访问其1级缓存而不是尽可能频繁地通过内存或其外部缓存,您可以获得巨大的性能提升。但这需要采取额外的预防措施,因为处理器将完全加载其缓存,并在每次需要时重新加载(从内存或级别2-3缓存)。 在并发数据结构的情况下,正如@David Schwartz所说,错误共享将迫使处理器更频繁地重新加载其缓存,因为一些数据可能被加载到存储器的其余部分,被修改,并强制整个缓存再次加载。