Вопрос

Как часто вы используете спин-блокировки в своем коде?Насколько часто можно столкнуться с ситуацией, когда использование цикла занятости фактически превосходит использование блокировок?
Лично, когда я пишу какой-то код, требующий потокобезопасности, я склонен сравнивать его с различными примитивами синхронизации, и, судя по всему, использование блокировок обеспечивает лучшую производительность, чем использование спин-блокировок.Независимо от того, как мало времени я на самом деле удерживаю блокировку, количество конфликтов, которые я получаю при использовании спин-блокировок, намного превышает количество, которое я получаю от использования блокировок (конечно, я провожу свои тесты на многопроцессорной машине).

Я понимаю, что спин-блокировку чаще можно встретить в «низкоуровневом» коде, но мне интересно узнать, найдете ли вы ее полезной даже в более высокоуровневом программировании?

Это было полезно?

Решение

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

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

Другие советы

По моему опыту, в C# «спин-блокировки» почти всегда были хуже, чем взятие блокировки — это редкий случай, когда спин-блокировки превосходят блокировку.

Однако это не всегда так..NET 4 добавляет System.Threading.SpinLock состав.Это дает преимущества в ситуациях, когда блокировка удерживается в течение очень короткого времени и неоднократно захватывается.Из документации MSDN на Структуры данных для параллельного программирования:

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

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

В своей работе в реальном времени, особенно с драйверами устройств, я довольно часто их использовал.Оказывается, что (когда я в последний раз замерял время) ожидание объекта синхронизации, такого как семафор, привязанного к аппаратному прерыванию, занимает не менее 20 микросекунд, независимо от того, сколько времени фактически требуется для возникновения прерывания.Одна проверка аппаратного регистра, отображаемого в памяти, за которой следует проверка RDTSC (чтобы обеспечить тайм-аут и не заблокировать машину) находится в диапазоне высоких наносекунд (по сути, в шуме).Для установления связи на аппаратном уровне, которое вообще не должно занимать много времени, действительно сложно обойти спин-блокировку.

Мой 2с:Если ваши обновления удовлетворяют некоторым критериям доступа, то они являются хорошими кандидатами на спин-блокировку:

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

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

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

Для практического использования я часто использую массивы из тысяч элементов, где обновления различных элементов массива могут безопасно происходить параллельно.Вероятность того, что два потока попытаются обновить один и тот же элемент одновременно, очень мала (низкая конкуренция), но мне нужна одна блокировка для каждого элемента (их у меня будет много).В этих случаях я обычно выделяю массив убайт того же размера, что и массив, который я обновляю параллельно, и реализую встроенные спин-блокировки как (на языке программирования D):

while(!atomicCasUbyte(spinLocks[i], 0, 1)) {}
    myArray[i] = newVal;
atomicSetUbyte(spinLocks[i], 0);

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

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

Обратите внимание на следующие моменты:

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

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

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

Я не могу придумать никакой причины использовать спин-блокировку в коде С#, работающем в обычной ОС.Занятые блокировки в основном являются пустой тратой на уровне приложения: вращение может привести к использованию всего временного интервала процессора, тогда как блокировка немедленно вызовет переключение контекста, если это необходимо.

Высокопроизводительный код, в котором количество потоков = количество процессоров/ядер, может принести пользу в некоторых случаях, но если вам нужна оптимизация производительности на этом уровне, вы, скорее всего, создадите 3D-игру следующего поколения, работающую на встроенной ОС с плохими примитивами синхронизации, создавая ОС/драйвер или в любом случае не использовать С#.

Я использовал спин-блокировки на этапе остановки мира сборщика мусора в своей работе. ХЛВМ проект, потому что они просты и это игрушечная виртуальная машина.Однако в этом контексте спин-блокировки могут оказаться контрпродуктивными:

Одна из ошибок производительности в сборщике мусора компилятора Glasgow Haskell настолько раздражает, что у нее есть имя "последнее замедление ядра".Это прямое следствие ненадлежащего использования спин-блокировок в сборщике мусора и усугубляется в Linux из-за его планировщика, но на самом деле этот эффект можно наблюдать всякий раз, когда другие программы конкурируют за процессорное время.

Эффект очевиден на втором графике. здесь и видно, что это влияет не только на последнее ядро. здесь, где программа Haskell видит снижение производительности за пределами пяти ядер.

Всегда помните об этих моментах при использовании спинлоки:

  • Быстрое выполнение пользовательского режима.
  • Синхронизирует потоки внутри одного процесса или нескольких процессов, если они находятся в общей памяти.
  • Не возвращается, пока объект не станет владельцем.
  • Не поддерживает рекурсию.
  • Во время ожидания потребляет 100% ресурсов процессора.

Я лично видел так много взаимоблокировок только потому, что кто-то подумал, что будет хорошей идеей использовать спин-блокировку.

Будьте очень осторожны при использовании спинлоков.

(Я не могу это подчеркнуть).

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top