为什么共享状态会降低性能?


19

我一直在并发编程的“不分担”原则下工作。本质上,我的所有工作线程都具有相同状态的不可变只读副本,它们之间从未共享(即使通过引用)。总体而言,这确实很好。

现在,有人引入了一个无锁单例缓存(例如静态字典),所有线程正在同时访问。由于字典在启动后永远不会更改,因此没有锁。没有任何线程安全问题,但是现在性能下降了。

问题是...由于没有锁,为什么引入此单例会导致性能下降?幕后到底发生了什么,可以解释这一点?

确认一下,唯一的改变就是访问这个新的单例,我只需注释掉对缓存的调用就可以可靠地重新创建它。


8
您是否已将分析器指向该代码?
Timo Geusch 2011年

2
除非您要对CLR以及Windows内核进行概要分析,否则概要分析不可能回答这个问题(对于普通程序员而言,这不是一件容易的事)。
伊比·拉格曼

1
@JoeGeeky好吧,我想这里对我唯一要做的就是+1和收藏!因为它们都是在毕竟间接的同一水平,并应符合在处理器缓存反正,等这似乎不可思议...
马克斯

2
FWIT我产生了几个线程并运行了一些计时器。我实例化了一个类,单例,lockedSingleton和dict <string,string>。在每个实例的第一个实例化之后,任何给定对象的连续运行大约需要2000ns。字典运行速度慢了2倍,可能是由构造函数代码引起的……它比锁本身慢。考虑到所有GC,操作系统对线程队列的处理以及其他开销……不确定是否可以真正回答这一问题。但是,从我的结果来看,我认为问题与Singletons无关。如果是像在MSDN上那样实现的,则不会。排除编译器优化。
P.Brian.Mackey 2011年

1
@JoeGeeky-另一个想法:使用缓存是否增加了间接级别?如果经常访问,则追逐额外的指针解引用(或MSIL等效项)可能会在本地间接间接复制上增加一些时间。
sdg

Answers:


8

不可变状态可能与某些可变的对象共享一条缓存行。在这种情况下,更改为附近的可变状态可能会导致强制此缓存行跨内核重新同步,这可能会降低性能。


3
这听起来像是false sharing您所描述的情况。为了隔离,我需要分析L2缓存。不幸的是,这些都是引用类型,因此,如果实际上这是事实,那么添加缓冲区空间将不是一个选择。
JoeGeeky 2011年

3

我会确保Equals()GetHashCode()用作字典键的对象方法没有意外的非线程友好的副作用。分析将在这里大有帮助。

如果您的键是字符串,那么也许就可以了:有传言说字符串的行为就像不可变的对象,但是出于某些优化的考虑,它们以可变的方式在内部实现,因此涉及多线程。

我会尝试将字典传递给使用它作为常规引用而不是单例的线程,以查看问题是在于字典的共享性还是单例性。(消除可能的原因。)

我还会尝试使用a ConcurrentDictionary而不是常规的Dictionary,以防万一其使用产生了一些令人惊讶的结果。如果ConcurrentDictionary结果表现比正常情况好得多或差得多,关于手头问题的事情有很多可以推测的Dictionary

如果以上都不是问题的根源,那么我想性能下降是由垃圾收集线程与其余线程之间发生某种奇怪的争用引起的,因为垃圾收集器正在尝试确定是否当线程正在访问字典时,是否需要处理字典中的对象。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.