Interlocked.Increment против блокировки в режиме отладки и в режиме выпуска

Я проверял, как Interlocked.Increment и lock ведут себя на архитектуре моего компьютера, потому что я прочитал следующие строки в эта статья.

Как переписано с Interlocked.Increment, метод должен выполняться быстрее, по крайней мере, на некоторых архитектурах.

Используя следующий код, я убеждаюсь, что стоит проверить блокировки в своих проектах.

var watch = new Stopwatch();
var locker = new object();
int counter = 0;

watch.Start();
for (int i = 0; i < 100000000; i++)
{
    lock (locker)
    {
        counter++;
    }
}
watch.Stop();
Console.WriteLine(watch.Elapsed.TotalSeconds);

watch.Reset();
counter = 0;

watch.Start();
for (int i = 0; i < 100000000; i++)
{
    Interlocked.Increment(ref counter);
}
watch.Stop();
Console.WriteLine(watch.Elapsed.TotalSeconds);

Я получаю стабильные результаты с приблизительными значениями 2,4 с для блокировки и 1,2 с для блокировки. Однако я был удивлен, обнаружив, что запуск этого кода в деблокированном режиме улучшает значение только для Interlocked примерно до 0,7 с, а время блокировки остается прежним. Почему это? Как оптимизирован Interlocked, когда в режиме деблокирования блокировка не работает?


person Ondrej Janacek    schedule 22.12.2013    source источник
comment
Как правило, все измерения производительности следует проводить только в режиме Release. Измерения производительности в режиме отладки не имеют значения. Прочитайте MSIL, предоставленный компилятором C#, возможно, код слишком сильно оптимизирован (например, Interlocked заменен на ++).   -  person Alex F    schedule 22.12.2013
comment
Неоспариваемая область блокировки нуждается в двух взаимосвязанных инструкциях. Цифры, которые вы измерили, прекрасно это показывают, по крайней мере, в режиме отладки. Кстати, ваш бенчмарк мало что значит, потому что стоимость синхронизации зависит от конкуренции за блокировку и строку кэша. Ваш тест предполагает, что его нет.   -  person usr    schedule 22.12.2013
comment
@usr Было бы здорово, если бы вы могли написать об этом больше и опубликовать в качестве ответа. Я не понимаю MSIL, как предлагает Алекс, и, поскольку я прочитал всю статью и до сих пор не понимаю ее полностью, хотелось бы получить дополнительную информацию по этой теме.   -  person Ondrej Janacek    schedule 22.12.2013
comment
@OndrejJanacek У меня нет ответа, потому что я не знаю, почему циклы ведут себя по-разному в режиме Release. Надо будет посмотреть на разборке. У меня сейчас нет времени на это расследование.   -  person usr    schedule 22.12.2013
comment
@usr Мне не нужен ответ прямо сейчас.   -  person Ondrej Janacek    schedule 22.12.2013
comment
@OndrejJanacek - вы будете удивлены, что MSIL относительно высокого уровня и легко читается. Просто введите ILDASM в окне командной строки VS и откройте исполняемый файл. Может быть интересно...   -  person Alex F    schedule 22.12.2013
comment
Тестирование производительности в режиме отладки — бессмысленная трата времени; ни одно из полученных вами чисел не будет иметь даже смутного смысла — если, конечно, ваши клиенты не будут запускать вашу программу в режиме отладки.   -  person Eric Lippert    schedule 22.12.2013


Ответы (1)


Вы должны посмотреть на сгенерированный машинный код, чтобы увидеть разницу, Отладка + Windows + Дизассемблирование. Отладочная версия вызова Interlocked.Increment():

   00FC27AD  call        7327A810 

Релизная версия сборки:

   025F279D  lock inc    dword ptr [ebp-24h] 

Или, другими словами, оптимизатор джиттера стал очень умным в сборке Release и заменил вызов вспомогательной функции одной машинной инструкцией.

Оптимизация просто не становится лучше, чем это. Та же самая оптимизация не может быть применена к вызову метода Monitor.Enter(), который находится под оператором lock, это довольно существенная функция, которая реализована в среде CLR и не может быть встроена. Он делает много вещей помимо Interlocked.Increment(), он позволяет операционной системе перепланировать, когда поток блокируется при попытке получить монитор, и поддерживает очередь ожидающих потоков. Это может быть очень важно для обеспечения хорошего параллелизма, но не в вашем тестовом коде, поскольку блокировка полностью неоспорима. Остерегайтесь синтетических тестов, которые не соответствуют реальному использованию.

person Hans Passant    schedule 22.12.2013