Сравнить и обменять - Compare-and-swap

В Информатика, сравнивать и менять местами (CAS) является атомный инструкция используется в многопоточность достигать синхронизация. Он сравнивает содержимое ячейки памяти с заданным значением и, только если они совпадают, изменяет содержимое этой ячейки памяти на новое заданное значение. Это делается как одна атомарная операция. Атомарность гарантирует, что новое значение рассчитывается на основе актуальной информации; если за это время значение было обновлено другим потоком, запись завершится неудачно. Результат операции должен указывать, выполнялась ли подстановка; это можно сделать либо с помощью простого логический ответ (этот вариант часто называют сравнивать и устанавливать), или путем возврата значения, считанного из ячейки памяти (не записанное в него значение).

Обзор

Операция сравнения и замены - это атомарная версия следующего псевдокод, где * обозначает доступ через указатель:[1]

функция cas (p: указатель на int, старый: int, новый: int) является    если * p ≠ старый вернуть false * p ← новое вернуть правда

Эта операция используется для реализации примитивы синхронизации любить семафоры и мьютексы,[1] а также более сложные алгоритмы без блокировки и ожидания. Морис Херлихи (1991) доказали, что CAS может реализовать больше этих алгоритмов, чем атомный читать, писать или получить и добавить, и предполагая, что[требуется разъяснение ] объем памяти, который может реализовать все из них.[2] CAS эквивалентен ссылка загрузки / магазин-условный, в том смысле, что постоянное количество вызовов одного примитива может использоваться для реализации другого в без ожидания манера.[3]

Алгоритмы, построенные на основе CAS, обычно читают некоторые ключевые области памяти и запоминают старое значение. На основе этого старого значения они вычисляют новое значение. Затем они пытаются поменять местами новое значение с помощью CAS, где сравнение проверяет, остается ли местоположение равным старому значению. Если CAS указывает, что попытка не удалась, ее необходимо повторить с самого начала: местоположение перечитывается, новое значение вычисляется повторно, и CAS выполняется снова. Исследователи обнаружили, что вместо того, чтобы немедленно повторять попытку после сбоя операции CAS, общая производительность системы может быть улучшена в многопроцессорных системах, где многие потоки постоянно обновляют некоторую конкретную общую переменную, если потоки, которые видят, что их CAS не работают, используют экспоненциальный откат - другими словами, подождите немного перед повторной попыткой CAS.[4]

Пример приложения: атомный сумматор

В качестве примера использования функции сравнения и обмена приведем алгоритм для атомарное увеличение или уменьшение целого числа. Это полезно во множестве приложений, использующих счетчики. Функция Добавить выполняет действие * р ← * р + а, атомарно (снова обозначая косвенный указатель *, как в C) и возвращает окончательное значение, сохраненное в счетчике. В отличие от cas псевдокодом выше, не требуется, чтобы любая последовательность операций была атомарной, за исключением cas.

функция add (p: указатель на int, a: int) возвращает int done ← false в то время как невыполнено значение ← * p // Даже эта операция не должна быть атомарной. готово ← cas (p, value, value + a) вернуть значение + a

В этом алгоритме, если значение *п изменяется после (или во время!) его выборки и до того, как CAS выполнит сохранение, CAS заметит и сообщит об этом факте, заставляя алгоритм повторить попытку.[5]

Проблема ABA

Некоторые алгоритмы, основанные на CAS, подвержены и должны решать проблему ложно положительный матч, или Проблема ABA. Возможно, что между моментом считывания старого значения и моментом попытки CAS некоторые другие процессоры или потоки изменят расположение памяти два или более раз, так что он получит битовый шаблон, который соответствует старому значению. Проблема возникает, если этот новый битовый шаблон, который выглядит точно так же, как старое значение, имеет другое значение: например, это может быть переработанный адрес или завернутый счетчик версий.

Общее решение - использовать CAS двойной длины (DCAS). Например. в 32-битной системе можно использовать 64-битный CAS. Вторая половина используется для удержания фишки. Часть операции сравнения сравнивает ранее прочитанное значение указателя. и счетчик с текущим указателем и счетчиком. Если они совпадают, происходит обмен - записывается новое значение, но новое значение имеет увеличенный счетчик. Это означает, что если ABA произошел, хотя значение указателя будет таким же, счетчик вряд ли будет таким же (для 32-битного значения, кратного 232 должны были произойти операции, в результате чего счетчик обернулся, и в этот момент значение указателя также должно было бы случайно совпадать).

Альтернативная форма этого (полезная для процессоров, в которых отсутствует DCAS) заключается в использовании индекса в свободном списке, а не полного указателя, например с 32-битным CAS используйте 16-битный индекс и 16-битный счетчик. Однако уменьшенная длина счетчиков делает ABA возможной на современных скоростях ЦП.

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

Более сложным, но более эффективным решением является реализация безопасного восстановления памяти (SMR). По сути, это сборка мусора без блокировки. Преимущество использования SMR заключается в гарантии того, что данный указатель будет существовать только один раз в любой момент времени в структуре данных, таким образом, проблема ABA полностью решена. (Без SMR будет использоваться что-то вроде freelist, чтобы обеспечить безопасный доступ ко всем элементам данных (без нарушений доступа к памяти), даже если они больше не присутствуют в структуре данных. С SMR только элементы, которые в настоящее время находятся в структура данных будет доступна).

Затраты и преимущества

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

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

В многопроцессорных архитектурах серверного уровня 2010-х годов сравнение и замена обходятся дешево по сравнению с простой нагрузкой, которая не обслуживается из кеша. В документе 2013 года указывается, что CAS всего в 1,15 раза дороже, чем загрузка без кеширования на Intel Xeon (Westmere-EX ) и в 1,35 раза на AMD Opteron (Маньи-Кур).[6]

Реализации

Сравнение-и-обмен (и сравнение-и-обмен-двойное) было неотъемлемой частью IBM 370 (и всех последующих) архитектур с 1970 года. Операционные системы, работающие на этих архитектурах, широко используют эту инструкцию для облегчения процессов (то есть системных и пользовательских задач) и процессора (то есть центральных процессоров) параллелизм устраняя в максимально возможной степени "инвалиды спиновые замки ", которые использовались в более ранних операционных системах IBM. Аналогичным образом, использование испытать и установить также был ликвидирован. В этих операционных системах новые единицы работы могут быть созданы «глобально» в глобальном списке приоритетов услуг или «локально» в локальном списке приоритетов услуг путем выполнения одной инструкции сравнения и замены. Это существенно улучшило быстродействие этих операционных систем.

в x86 (поскольку 80486 ) и Itanium архитектур это реализовано как сравнивать и обмениваться (CMPXCHG) инструкция (на мультипроцессоре ЗАМОК необходимо использовать префикс).

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

Операции атомарного счетчика и атомарной битовой маски в ядре Linux обычно используют в своей реализации инструкцию сравнения и замены. SPARC-V8 и PA-RISC архитектуры - две из очень немногих последних архитектур, которые не поддерживают CAS аппаратно; порт Linux на эти архитектуры использует спин-блокировка.[7]

Реализация на C

Многие компиляторы C поддерживают использование функции сравнения и замены либо с C11<stdatomic.h> функции,[8]или какое-то нестандартное расширение C этого конкретного компилятора C,[9]или путем вызова функции, написанной непосредственно на языке ассемблера, с помощью инструкции сравнения и замены.

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

int compare_and_swap(int* рег, int Oldval, int Newval){    АТОМНЫЙ();    int old_reg_val = *рег;    если (old_reg_val == Oldval)        *рег = Newval;    END_ATOMIC();    вернуть old_reg_val;}

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

Например, протокол выборов может быть реализован так, что каждый процесс проверяет результат compare_and_swap против своего собственного PID (= newval). Выигрышный процесс находит compare_and_swap возвращает начальное значение, отличное от PID (например, ноль). Проигравшим будет возвращен выигрышный PID.

bool compare_and_swap(int *накопить, int *dest, int Newval){    если (*накопить == *dest) {        *dest = Newval;        вернуть правда;    } еще {        *накопить = *dest;        вернуть ложный;    }}

Это логика в Руководстве по программному обеспечению Intel, том 2A.

Расширения

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

Двойное сравнение и замена (DCAS)
Сравнивает две несвязанные ячейки памяти с двумя ожидаемыми значениями и, если они равны, устанавливает для обоих местоположений новые значения. Обобщение DCAS на несколько (несмежных) слов называется MCAS или CASN. DCAS и MCAS представляют практический интерес с точки зрения удобной (параллельной) реализации некоторых структур данных, таких как выводит из очереди или деревья двоичного поиска.[10][11] DCAS и MCAS могут быть реализованы, однако, с использованием более выразительного оборудования. транзакционная память[12] присутствует в некоторых последних процессорах, таких как IBM МОЩНОСТЬ8 или в процессорах Intel, поддерживающих Расширения транзакционной синхронизации (TSX).
Двойное сравнение и замена
Работает с двумя соседними ячейками размером с указатель (или, что то же самое, с одним местоположением, в два раза превышающим размер указателя). На более поздних процессорах x86 инструкции CMPXCHG8B и CMPXCHG16B[13] выполняют эту роль, хотя ранние 64-разрядные процессоры AMD не поддерживали CMPXCHG16B (современные процессоры AMD поддерживают). Некоторые материнские платы Intel от Ядро 2 эпохи также затрудняют его использование, хотя процессоры его поддерживают. Эти вопросы были в центре внимания при запуске Windows 8.1 поскольку для этого требовалась аппаратная поддержка CMPXCHG16B.[14]
Одиночное сравнение, двойной обмен
Сравнивает один указатель, но записывает два. Это реализует инструкция Itanium cmp8xchg16,[15] где два написанных указателя находятся рядом.
Сравнение и замена нескольких слов
Является обобщением нормального сравнения и обмена. Его можно использовать для атомарной замены произвольного количества произвольно расположенных ячеек памяти. Обычно сравнение и замена нескольких слов реализуется в программном обеспечении с использованием обычных операций сравнения и замены двойной ширины.[16] Недостатком такого подхода является отсутствие масштабируемости.

Смотрите также

использованная литература

  1. ^ а б Маллендер, Сапе; Кокс, Расс (2008). Семафоры в Plan 9 (PDF). 3-й международный семинар по План 9.
  2. ^ Херлихи, Морис (январь 1991). «Синхронизация без ожидания» (PDF). ACM Trans. Программа. Lang. Syst. 13 (1): 124–149. CiteSeerX  10.1.1.56.5659. Дои:10.1145/114005.102808. Получено 2007-05-20.
  3. ^ Дж. Х. Андерсон и М. Мойр. «Универсальные конструкции для многообъектных операций». В Proc. 14-й ежегодный симпозиум ACM по принципам распределенных вычислений, pages 184–193, 1995. См. их Таблицу 1, Рисунки 1 и 2 и, в частности, Раздел 2.
  4. ^ а б Дайс, Дэйв; Хендлер, Дэнни; Мирский, Илья (2013). «Упрощенное управление конфликтами для эффективных операций сравнения и обмена». arXiv:1305.5800 [cs.DC ].
  5. ^ Гетц, Брайан (23 ноября 2004 г.). "Теория и практика Java: атомарность". IBM developerWorks.
  6. ^ Тюдор Давид, Рашид Геррауи и Василиос Тригонакис. «Все, что вы всегда хотели знать о синхронизации, но боялись спросить». Труды двадцать четвертого симпозиума ACM по принципам операционных систем. ACM, 2013, стр. 33-48. Подробно на стр. 34
  7. ^ Дэвид С. Миллер.«Семантика и поведение атомарных операций и операций с битовой маской для разработчиков порта Linux» В архиве 2012-03-20 на Wayback Machine.
  8. ^ http://en.cppreference.com/w/c/atomic/atomic_compare_exchange
  9. ^ «Расширения GNU C к семейству языков C: встроенные функции для доступа к атомарной памяти»
  10. ^ Саймон Доэрти и др. "DCAS - не лучшая цель для разработки неблокирующего алгоритма ". 16-й ежегодный симпозиум ACM по параллелизму в алгоритмах и архитектурах, 2004 г., стр. 216–224. Дои:10.1145/1007912.1007945
  11. ^ Кейр Фрейзер (2004), "Практическая свобода от замков" UCAM-CL-TR-579.pdf
  12. ^ Дэйв Дайс, Йоси Лев, Марк Мойр, Дэн Нуссбаум и Марек Ольшевски. (2009) «Ранний опыт реализации коммерческой аппаратной транзакционной памяти». Технический отчет Sun Microsystems (60 стр.) SMLI TR-2009-180. Короткая версия появилась на ASPLOS’09. Дои:10.1145/1508244.1508263. В полном объеме отчета обсуждается, как реализовать DCAS с использованием HTM, в разделе 5.
  13. ^ «Руководство разработчика программного обеспечения для архитектур Intel 64 и IA-32, том 2A: Справочник по набору инструкций, A – M» (PDF). Получено 2007-12-15.
  14. ^ http://www.pcworld.com/article/2058683/new-windows-8-1-requirements-strand-some-users-on-windows-8.html
  15. ^ «Руководство разработчика программного обеспечения Intel Itanium, том 3: Справочник по набору инструкций» (PDF). Получено 2007-12-15.
  16. ^ «Практическая операция сравнения и замены нескольких слов» (PDF). Получено 2009-08-08.

внешние ссылки

Базовые алгоритмы, реализованные с использованием CAS

Реализации CAS