Насколько дорого стоит инструкция блокировки?

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

private object mutex = new object();

public void Count(int amount)
{
 lock(mutex)
 {
  done += amount;
 }
}

Но мне было интересно... насколько дорого стоит блокировка переменной? Каковы негативные последствия для производительности?

Ответ 1

Вот статья, которая входит в стоимость. Короткий ответ - 50 нс.

Ответ 2

Технический ответ заключается в том, что его невозможно количественно оценить, он в значительной степени зависит от состояния буферов обратной записи памяти процессора и количества данных, которые собирал сборщик, должен быть отброшен и перечитан. Которые являются очень недетерминированными. Я использую 150 циклов ЦП в качестве приближения к обратной оболочке, что позволяет избежать серьезных разочарований.

Практический ответ: это waaaay дешевле, чем количество времени, которое вы будете записывать при отладке вашего кода, когда вы думаете, что можете пропустить блокировку.

Чтобы получить жесткий номер, вам придется измерять. Visual Studio имеет гладкий concurrency анализатор, доступный как расширение.

Ответ 3

Боже мой!

Кажется, правильный ответ помечен здесь как ОТВЕТ, по своей сути неправильный! Я хотел бы попросить автора ответа, с уважением, прочитать связанную статью до конца. статья

Автор статьи из статьи оценивался только на двухъядерном процессоре, а в первом измерительном случае он измерял блокировку только с одной нитью и результат составил около 50 нс на доступ к блокировке.

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

Итак, автор говорит, что с двумя потоками на Dual Core блокировки стоят 120 нс, а с 3 потоками он переходит на 180 нс. Таким образом, он явно зависит от количества одновременно доступных потоков, и еще хуже.

Итак, это просто, это не 50 нс, если только это ни один поток, где блокировка становится бесполезной.

Еще одна проблема для рассмотрения заключается в том, что она измеряется как среднее время!

Если бы время итераций было измерено, было бы даже время между 1 мс до 20 мс, просто потому, что большинство было быстрым, но несколько потоков ожидали процессорного времени и несли даже миллисекундные задержки.

Это плохая новость для любого приложения, которое требует высокой пропускной способности, низкой латентности.

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

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

Ответ 4

Это не отвечает на ваш запрос о производительности, но я могу сказать, что .NET Framework предлагает метод Interlocked.Add, который позволит вам добавить свой amount к вашему элементу done без ручной блокировки другого объекта.

Ответ 5

lock (Monitor.Enter/Exit) очень дешев, дешевле, чем альтернативы, такие как Waithandle или Mutex.

Но что, если бы это было (немного) медленно, вы бы скорее предложили быструю программу с неправильными результатами?

Ответ 6

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

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace LockPerformanceConsoleApplication
{
    class Program
    {
        static void Main(string[] args)
        {
            var stopwatch = new Stopwatch();
            const int LoopCount = (int) (100 * 1e6);
            int counter = 0;

            for (int repetition = 0; repetition < 5; repetition++)
            {
                stopwatch.Reset();
                stopwatch.Start();
                for (int i = 0; i < LoopCount; i++)
                    lock (stopwatch)
                        counter = i;
                stopwatch.Stop();
                Console.WriteLine("With lock: {0}", stopwatch.ElapsedMilliseconds);

                stopwatch.Reset();
                stopwatch.Start();
                for (int i = 0; i < LoopCount; i++)
                    counter = i;
                stopwatch.Stop();
                Console.WriteLine("Without lock: {0}", stopwatch.ElapsedMilliseconds);
            }

            Console.ReadKey();
        }
    }
}

Вывод:

With lock: 2013
Without lock: 211
With lock: 2002
Without lock: 210
With lock: 1989
Without lock: 210
With lock: 1987
Without lock: 207
With lock: 1988
Without lock: 208

Ответ 7

Существует несколько разных способов определения "стоимости". Фактические накладные расходы на получение и освобождение блокировки; как пишет Джейк, это незначительно, если эта операция не выполняется миллионы раз.

Более актуальным является влияние этого на ход выполнения. Этот код может вводиться только одним потоком за раз. Если у вас есть 5 потоков, выполняющих эту операцию на регулярной основе, 4 из них в конечном итоге ожидают, что блокировка будет выпущена, а затем, чтобы быть первым потоком, запланированным для ввода этого фрагмента кода после освобождения блокировки. Таким образом, ваш алгоритм будет сильно страдать. Насколько это зависит от алгоритма и от того, как часто вызывается операция. Вы не можете избежать этого, не вводя условия гонки, но вы можете улучшить его, минимизируя количество вызовов заблокированного кода.