哪个更快,带锁的函数调用还是虚拟调用?

时间:2010-01-21 15:59:23

标签: c# .net performance locking virtual-method

我有一个当前不需要线程安全的类,但将来我们可能想要创建一个线程安全的版本。我看待它的方式,我现在可以通过在相关函数周围放置锁来使其成为线程安全的,或者我现在可以将它们设置为虚拟,并在稍后的后代类中覆盖它们。也就是说,今天我可以这样做:

public void DoStuff()
{
    lock (this.SyncRoot)
    {
        // Do stuff...
    }
}

或者我可以这样做:

public virtual void DoStuff()
{
    // Do stuff...
}

今天哪个选项可以更快地完成这些工作?

7 个答案:

答案 0 :(得分:4)

虚函数调用基本上是数组查找加上间接函数调用。如果虚拟调用是在循环中发生的,即如果从同一实例上的相同位置多次调用虚函数调用,那么在大多数迭代中它将不比非内联正常函数调用慢。现代CPU分支预测器预测虚拟函数调用的目标,并且在获取函数的地址的同时推测性地执行该目标。

另一方面,锁定总是涉及至少一次或两次原子操作。这样的操作几乎可以保证在CPU管道上乱跑,因为它们需要内存屏障。

答案 1 :(得分:2)

第二,因为虚拟通话非常便宜(锁也是一个额外的通话,这比虚拟通话本身更贵)。

此外,第二个让你在需要时完全实现锁定。

答案 2 :(得分:2)

如果你打算让DoStuff同步(并保证它适用于任何给定的子类),那么你最好而不是使它virtual和使用protected virtual成员来完成实际工作。

public void DoStuff()
{
    lock(this.SyncRoot)
    {
        InternalDoStuff();
    }
}

protected virtual void InternalDoStuff()
{
    // do stuff
}

这也为您提供了 lock在当前代码中的选项(意味着DoStuff只调用InternalDoStuff而没有其他代码),但仍然可以在以后将其放入,而无需触及您继承的代码。

至于速度,lock语句的放置不会有任何影响。

答案 3 :(得分:1)

虚拟呼叫几乎肯定会更快。虚拟呼叫涉及额外的间接级别。锁通常涉及切换到内核模式 - 保守估计至少要慢100倍。

答案 4 :(得分:1)

第二个。如果您今天不需要线程安全,请不要这样做。公开SyncRoot属性,以便将来可以将该方法设置为线程安全的。但请确保在文档中明确指出该方法不是线程安全的。

答案 5 :(得分:1)

Egads,我找不到引用,也许是在Joe Duffy的书中,但Lock可能是一个无操作,直到另一个线程开始命中它(即懒惰的创建)。

此外,此锁无论如何都不会进入内核模式,它基于Interlocked ### API。

最后,了解这些主题很有趣,但最好的办法是始终为自己的代码计时。

答案 6 :(得分:1)

我测试了VB SyncLock,它的速度差了近300倍。