Что быстрее: вызов функции с блокировкой или виртуальный вызов? - PullRequest
1 голос
/ 21 января 2010

У меня есть класс, который в настоящее время не должен быть поточно-ориентированным, но в будущем мы можем захотеть сделать поточно-ориентированную версию. На мой взгляд, я могу либо сделать его поточно-безопасным сейчас, установив блокировки вокруг соответствующих функций, либо я могу сделать их виртуальными сейчас, а позже установить блокировки вокруг них в переопределенных классах потомков. То есть сегодня я могу сделать это:

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

Или я могу сделать это:

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

Какая опция сделает вещи быстрее, сегодня?

Ответы [ 7 ]

4 голосов
/ 21 января 2010

Виртуальный вызов функции - это в основном поиск в массиве плюс косвенный вызов функции.Если виртуальный вызов происходит в цикле, т. Е. Если вызов виртуальной функции вызывается несколько раз из одного и того же места в одном и том же экземпляре, то на большинстве итераций он будет не медленнее, чем обычный вызов функции без встроенной функции.Современные предикторы ветвей ЦП предсказывают цель вызова виртуальной функции и спекулятивно выполняют эту цель параллельно с извлечением адреса функции.

С другой стороны, замок всегда включает по крайней мере одну атомную операцию или две под капотом.Такие операции в значительной степени гарантируют негативные последствия для конвейера ЦП, поскольку для них требуются барьеры памяти.

2 голосов
/ 21 января 2010

Если вы намереваетесь синхронизировать DoStuff (и гарантируете его для любого подкласса), тогда вам лучше , а не , сделав его virtual и используя protected virtual член, чтобы сделать фактическую работу.

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

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

Это также дает вам возможность не lock вводить в текущем коде (это означает, что DoStuff просто вызывает InternalDoStuff без другого кода), но все еще в состоянии вставить его в позже, не трогая ваш унаследованный код.

Что касается скорости, размещение оператора lock не окажет никакого влияния.

2 голосов
/ 21 января 2010

Второе, поскольку виртуальный вызов довольно дешев (блокировка также является дополнительным вызовом, который будет дороже, чем сам виртуальный вызов).

Кроме того, второй предоставляет вам возможность реализовать блокировку именно тогда, когда вам это нужно.

1 голос
/ 23 января 2010

Я тестировал VB SyncLock, и он работал почти в 300 раз медленнее.

1 голос
/ 21 января 2010

Egads, я не могу найти ссылку, возможно, она есть в книге Джо Даффи, но Замок может быть неактивным, пока другой поток не начнет его нажимать (т.е. ленивое создание).

Кроме того, эта блокировка в любом случае не входит в режим ядра, основана на интерфейсе Interlocked ### API.

Наконец, размышлять над этими темами - весело, но самое лучшее, что нужно сделать, - это всегда определять время своего кода.

1 голос
/ 21 января 2010

второй. Если вам сегодня не нужна безопасность потоков, не делайте этого. Предоставьте свойство SyncRoot, чтобы в будущем метод можно было сделать потокобезопасным. Но не забудьте четко указать в документации, что этот метод не является потокобезопасным.

1 голос
/ 21 января 2010

Виртуальный звонок почти наверняка будет быстрее. Виртуальный вызов предполагает дополнительный уровень косвенности. Блокировка обычно включает в себя переключение в режим ядра - цифра, по крайней мере, в 100 раз медленнее, если говорить осторожно.

...