Подтвердить что ты не робот

ConcurrentDictionary <> производительность при непонимании одного потока?

Связанная информация:

AFAIK, классы совпадающего стека, очереди и пакета реализуются внутри со связанными списками.
И я знаю, что конкуренция гораздо меньше, потому что каждый поток несет ответственность за свой собственный связанный список. В любом случае, мой вопрос касается ConcurrentDictionary<,>

Но я тестировал этот код: (единственный поток)

Stopwatch sw = new Stopwatch();
sw.Start();

    var d = new ConcurrentDictionary < int,  int > ();
    for(int i = 0; i < 1000000; i++) d[i] = 123;
    for(int i = 1000000; i < 2000000; i++) d[i] = 123;
    for(int i = 2000000; i < 3000000; i++) d[i] = 123;
    Console.WriteLine("baseline = " + sw.Elapsed);

sw.Restart();

    var d2 = new Dictionary < int, int > ();
    for(int i = 0; i < 1000000; i++)         lock (d2) d2[i] = 123;
    for(int i = 1000000; i < 2000000; i++)   lock (d2) d2[i] = 123;
    for(int i = 2000000; i < 3000000; i++)   lock (d2) d2[i] = 123;
    Console.WriteLine("baseline = " + sw.Elapsed);

sw.Stop();

Результат: (проверено много раз, те же значения (+/-)).

baseline = 00:00:01.2604656
baseline = 00:00:00.3229741

Вопрос:

Что делает ConcurrentDictionary<,> намного медленнее в одной потоковой среде?

Мой первый инстинкт заключается в том, что lock(){} будет всегда медленнее. но, видимо, это не так.

4b9b3361

Ответ 1

Наиболее вероятная причина, по которой ConcurrentDictionary просто больше накладных расходов, чем Dictionary для той же операции. Это действительно верно, если вы вникнете в источники

  • Он использует блокировку для индексатора
  • Он использует volatile write
  • Он должен выполнять атомарную запись значений, которые не гарантируют быть атомарными в .Net
  • У него есть дополнительные ветки в стандартной процедуре добавления (взять блокировку, сделать атомарную запись)

Все эти затраты понесены независимо от количества потоков, на которых оно используется. Эти затраты могут быть индивидуально небольшими, но не бесплатными и со временем складываться

Ответ 2

Ну, ConcurrentDictionary допускает возможность того, что он может использоваться несколькими потоками. Мне кажется вполне разумным, что для этого требуется больше внутренней домашней работы, чем то, что предполагает, что он может уйти, не беспокоясь о доступе из нескольких потоков. Я был бы очень удивлен, если бы это сработало наоборот: если бы более безопасная версия всегда была быстрее, почему бы вам использовать менее безопасную версию?

Ответ 3

ConcurrentDictionary<> создает внутренний набор объектов блокировки при создании (это определяется concurrencyLevel, среди других факторов) - этот набор объектов блокировки используется для управления доступом к внутренним структурам ковша в последовательности мелкозернистые замки.

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

Ответ 4

ConcurrentDictionary vs. Dictionary

В общем, используйте System.Collections.Concurrent.ConcurrentDictionary в любой сценарий, в котором вы добавляете и обновляете ключи или значения одновременно из нескольких потоков. В сценариях, связанных с частыми обновлений и относительно немного читать, ConcurrentDictionary обычно предлагает скромные преимущества. В сценариях, которые включают многие чтения и многие обновления, ConcurrentDictionary как правило, значительно быстрее на компьютерах с любым количеством ядра.

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

Если вы читаете только ключ или значения, словарь быстрее, потому что синхронизация не требуется, если словарь не изменяя любые потоки.

Ссылка: https://msdn.microsoft.com/en-us/library/dd997373%28v=vs.110%29.aspx

Ответ 5

ОБНОВЛЕНИЕ: последний ConcurrentDictionary значительно улучшился и предлагает тот же профиль производительности, что и моя реализация, с большим количеством функций, поэтому я рекомендую использовать его вместо этого :)

Я только что написал о моей безблокировочной поточно-ориентированной реализации словаря для копирования при записи здесь:

http://www.singulink.com/CodeIndex/post/fastest-thread-safe-lock-free-dictionary

Это очень быстро для быстрых циклов записи, и поиск обычно выполняется на 100% стандартной скорости Dictionary без блокировки. Если вы время от времени пишете и часто читаете, это самый быстрый вариант из доступных.

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

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

Моя реализация обеспечивает максимальную производительность чтения, устраняя необходимость в каких-либо блокировках чтения при нормальных обстоятельствах, пока обновления словаря не производятся. Компромисс заключается в том, что словарь необходимо копировать и заменять после применения обновлений (это делается в фоновом потоке), но если вы не пишете часто или пишете только один раз во время инициализации, то компромисс определенно стоит Это.

Ответ 6

  Что делает ConcurrentDictionary<,> намного медленнее в однопоточной среде?

Расходы на оборудование, необходимые для его ускорения в многопоточных средах.

Мой первый инстинкт заключается в том, что lock(){} будет всегда медленнее. но, видимо, это не так.

lock очень дешево, когда не оспаривается. Вы можете lock миллион раз в секунду, и ваш процессор даже не заметит, при условии, что вы делаете это из одного потока. То, что убивает производительность в многопоточных программах, - это конкуренция за блокировки. Когда несколько потоков яростно конкурируют за один и тот же lock, почти все они должны ждать, пока удачливый, удерживающий блокировку, освободит ее. Именно здесь сияет ConcurrentDictionary с его гранулярной реализацией блокировки. И чем больше у вас параллелизма (чем больше процессоров/ядер), тем больше он светит.

Ответ 7

Ваш тест неверен: вы должны остановить секундомер раньше!

        Stopwatch sw = new Stopwatch();      
        sw.Start();
        var d = new ConcurrentDictionary<int, int>();
        for (int i = 0; i < 1000000; i++) d[i] = 123;
        for (int i = 1000000; i < 2000000; i++) d[i] = 123;
        for (int i = 2000000; i < 3000000; i++) d[i] = 123;
        sw.Stop();
        Console.WriteLine("baseline = " + sw.Elapsed);



        sw.Start();
        var d2 = new Dictionary<int, int>();
        for (int i = 0; i < 1000000; i++) lock (d2) d2[i] = 123;
        for (int i = 1000000; i < 2000000; i++) lock (d2) d2[i] = 123;
        for (int i = 2000000; i < 3000000; i++) lock (d2) d2[i] = 123;
        sw.Stop();
        Console.WriteLine("baseline = " + sw.Elapsed);

        sw.Stop();

- Выход: