How to avoid slowdown due to locked code?
我想知道,即使代码从未执行过,一段锁定的代码如何能够减慢我的代码速度。下面是一个例子:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 
 | public void Test_PerformanceUnit()
{ 
    Stopwatch sw = new  Stopwatch(); 
    sw.Start(); 
    Random r = new  Random();
    for (int  i = 0;  i < 10000;  i++)
    { 
        testRand( r);
    } 
    sw.Stop(); 
    Console.WriteLine( sw.ElapsedTicks);
}
public object  testRand( Random r)
{
    if ( r.Next(1) > 10)
    {
        lock(this) {
            return null;
        }
    }
    return  r;
} | 
这个代码在我的机器上运行大约1300毫秒。如果我们移除锁块(但保留其主体),我们得到750毫秒。几乎是双倍,即使代码从未运行!
当然,这个代码什么也不做。在类中添加一些惰性初始化时,我注意到了这个问题,在这个类中,代码检查对象是否已初始化,是否未初始化。问题是,即使在第一次调用之后,初始化也会被锁定并减慢所有操作。
我的问题是:
为什么会这样?
如何避免减速
		
		
- 除非你打算集中使用lock,否则我不会担心。
- 我得到了类似的结果,但滴答声是100纳秒。两次运行都需要大约0毫秒(即,如果打印sw.ElapseMilliseconds)。此"减速"(约0.00006)可能是由于lock包含一个try/finally块,该块可能在调用方法时设置。尝试将testRand的内容放在循环本身中;此时您将几乎看不到任何减速。
- 你试过用AggressiveInline标记这个方法吗?可能是锁定代码使该方法对于正常的内联来说太大了。.NET抖动使用基于IL代码大小的相当愚蠢的启发式方法进入。
- +1、有趣的问题。
- 不,说真的:滴答声不是毫秒!
- 这里也有类似的话题。调查结果应该非常一致。
- @DLEV这种类型的勾选取决于CPU。不是常数100 ns。
- 你说得对,我把两种不同的虱子混为一谈。但滴答声仍然不是女士。
- 要做的最有用的事情是在十六进制编辑器中检查编译后的代码。如果要锁定某个对象,则编译后的exe或dll必须在预期此事件时执行某些操作。我太笨了,不知道到底是什么,但那是我的两分钱。
- 我追踪到了一份try{}finally{}声明。如果你使用它(即使是空的,没有嵌入代码),同样的速度会减慢。lock语句实现try{}finally{}。
- 另一个有启发性答案的问题:stackoverflow.com/questions/6029804/&hellip;
- 您显示的是,这个锁会使您的程序慢一点。当然可以。如果你添加一些其他的说明,你会得到类似的效果。
- 关于您的实际用例,您可能希望研究lazy类以满足您的lazy初始化需求。
- 看看乔恩·斯基特关于类似话题的答案。我可以确认,如果我运行为x64平台编译的示例,性能下降仅为15-20%。
- @阿里,我不明白为什么经济放缓会很明显。我们说的是不在这里执行的死代码。
- @Ramhound锁内的代码不是由程序逻辑执行的。这就是我不理解经济放缓的原因。
 
	 
关于它发生的原因,评论中已经讨论过:这是由于lock生成的try ... finally的初始化。
为了避免这种速度减慢,您可以将锁定特性提取到一个新方法中,这样,只有在实际调用该方法时才会初始化锁定机制。
我用这个简单的代码来尝试:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 
 | public object testRand(Random r){
 if (r.Next(1) > 10)
 {
 return LockingFeature();
 }
 return r;
 }
 
 private object LockingFeature()
 {
 lock (_lock)
 {
 return null;
 }
 }
 | 
以下是我的时间(滴答声):
| 12
 3
 
 | your code, no lock   : ~500your code, with lock : ~1200
 my code              : ~500
 | 
编辑:我的测试代码(比没有锁的代码慢一点)实际上是在静态方法上的,当代码在一个对象的"内部"运行时,时间是相同的。我根据那个确定了时间。
		
		
- 谢谢你的回答,这正是我要找的。在我的测试中,您的解决方案比lock内联运行得更快,但比仅使用return null运行得慢。我将方法LockingFeature定义为virtual,以避免代码嵌入,我获得了100%的性能回报。
- @Pieroxy——关于第一次测试的另一个问题是,带锁的testRand()版本也需要更长的时间来进行JIT。所以你可以在Stopwatch开始之前,只调用一次testRand(),把它从等式中去掉(可以说,这是一种预热jit编译器的方法)。这显著缩小了差距。不过,Zonko的代码是一种相当巧妙的处理方法。