Спин-замки всегда требуют барьер памяти? Дорого ли крутится барьер памяти?
Я написал некоторый код без блокировки, который отлично работает с локальным чтением, в большинстве случаев.
Обязательно ли местное вращение при чтении из памяти означает, что я ВСЕГДА должен вставлять барьер памяти перед вращением при чтении?
(Чтобы проверить это, мне удалось создать комбинацию чтения / записи, которая приводит к тому, что читатель никогда не видит записанное значение при определенных весьма специфических условиях - выделенный ЦП, процесс, подключенный к ЦП, оптимизатор включился полностью, никакой другой работы сделано в цикле - поэтому стрелки указывают в этом направлении, но я не совсем уверен в стоимости вращения через барьер памяти.)
Какова стоимость прокрутки через барьер памяти, если в буфере хранения кеша нечего очищать? т.е. весь процесс делает (в C)
while ( 1 ) {
__sync_synchronize();
v = value;
if ( v != 0 ) {
... something ...
}
}
Правильно ли предположить, что это бесплатно и не будет перегружать шину памяти каким-либо трафиком?
Еще один способ сделать это - спросить: делает ли барьер памяти что-то большее, чем: очищает буфер хранилища, применяет к нему недействительные данные и не позволяет компилятору переупорядочивать операции чтения / записи через свое местоположение?
Разбирая, __sync_synchronize(), по-видимому, переводится в:
lock orl
Из руководства Intel (аналогично туманному для неофита):
Volume 3A: System Programming Guide, Part 1 -- 8.1.2
Bus Locking
Intel 64 and IA-32 processors provide a LOCK# signal that
is asserted automatically during certain critical memory
operations to lock the system bus or equivalent link.
While this output signal is asserted, requests from other
processors or bus agents for control of the bus are
blocked.
[...]
For the P6 and more recent processor families, if the
memory area being accessed is cached internally in the
processor, the LOCK# signal is generally not asserted;
instead, locking is only applied to the processor’s caches
(see Section 8.1.4, “Effects of a LOCK Operation on
Internal Processor Caches”).
Мой перевод: "когда вы говорите LOCK, это будет дорого, но мы делаем это только там, где это необходимо".
@BlankXavier:
Я проверил, что если записывающее устройство явно не выталкивает запись из буфера хранилища и это единственный процесс, работающий на этом процессоре, читатель может никогда не увидеть влияние записывающего устройства (я могу воспроизвести его с помощью тестовой программы, но как я упоминал выше, это происходит только с определенным тестом, с конкретными параметрами компиляции и выделенными назначениями ядра - мой алгоритм работает нормально, только когда мне стало интересно, как это работает, и я написал явный тест, который, как я понял, потенциально мог иметь проблема в будущем).
Я думаю, что по умолчанию простые записи - это записи WB (Write Back), что означает, что они не сбрасываются немедленно, но чтения принимают свое последнее значение (я думаю, что они называют это "пересылка хранилища"). Поэтому я использую инструкцию CAS для писателя. Я обнаружил в руководстве Intel все эти различные типы реализаций записи (UC, WC, WT, WB, WP), Intel vol 3A, глава 11-10, все еще изучая их.
Моя неопределенность на стороне читателя: я понимаю из статьи МакКенни, что есть также очередь недействительных данных, очередь входящих недействительных сообщений из шины в кэш. Я не уверен, как эта часть работает. В частности, вы, похоже, подразумеваете, что циклическое выполнение обычного чтения (т. Е. Без блокировки, без барьера и использование volatile только для того, чтобы оптимизатор оставил чтение после компиляции) будет каждый раз проверяться в "очереди недействительности" (если такая вещь существует). Если простое чтение недостаточно хорошо (т. Е. Может прочитать старую строку кэша, которая все еще выглядит действительной в ожидании аннулирования в очереди (это звучит немного неуместно и для меня, но как тогда работают очереди аннулирования?)), Тогда атомарное чтение будет необходимо, и мой вопрос: в этом случае, это повлияет на автобус? (Я думаю, вероятно, нет.)
Я все еще читаю руководство по Intel и, несмотря на большое обсуждение переадресации магазина, я не нашел хорошего обсуждения очередей аннулирования. Я решил преобразовать свой C-код в ASM и поэкспериментировать, я думаю, что это лучший способ понять, как это работает.
3 ответа
Инструкция "xchg reg,[mem]" сообщит о намерении блокировки через вывод LOCK ядра. Этот сигнал проходит сквозь другие ядра и кешируется до шин мастеринга шин (варианты PCI и т. Д.), Которые завершают свою работу, и в конечном итоге вывод LOCKA (подтверждение) будет сигнализировать ЦПУ, что xchg может завершить работу. Затем сигнал LOCK отключается. Эта последовательность может занять много времени (сотни циклов процессора или более) для завершения. После этого соответствующие строки кэша других ядер будут признаны недействительными, и у вас будет известное состояние, то есть состояние, синхронизированное между ядрами.
Инструкция xchg - это все, что необходимо для реализации атомарной блокировки. Если сама блокировка успешна, у вас есть доступ к ресурсу, для которого вы определили блокировку, к которой можно получить доступ. Таким ресурсом может быть область памяти, файл, устройство, функция или что-то еще. Тем не менее, программист всегда должен писать код, который использует этот ресурс, когда он заблокирован, а не когда нет. Обычно кодовая последовательность после успешной блокировки должна быть как можно короче, чтобы другому коду было как можно меньше мешать получить доступ к ресурсу.
Имейте в виду, что если блокировка не была успешной, вам нужно повторить попытку, выпустив новый xchg.
"Lock free" - привлекательная концепция, но она требует устранения общих ресурсов. Если ваше приложение имеет два или более ядер одновременно, чтение и запись по общему адресу памяти "без блокировки" не вариант.
Я, возможно, не правильно понял вопрос, но...
Если вы вращаетесь, одной из проблем является компилятор, оптимизирующий ваше вращение. Летучие решает это.
Барьер памяти, если он у вас есть, будет выдан писателем на спин-блокировку, а не читателем. На самом деле автору не нужно использовать один - это гарантирует, что запись будет немедленно удалена, но в любом случае она скоро закончится.
Барьер препятствует тому, чтобы поток, выполняющий этот код, переупорядочил его местоположение, которое является его другой стоимостью.
Имейте в виду, что барьеры обычно используются для упорядочивания наборов обращений к памяти, поэтому вашему коду, вероятно, могут понадобиться барьеры в других местах. Например, весьма вероятно, что барьерное требование будет выглядеть так:
while ( 1 ) {
v = pShared->value;
__acquire_barrier() ;
if ( v != 0 ) {
foo( pShared->something ) ;
}
}
Этот барьер будет препятствовать загрузке и хранению в блоке if (то есть: pShared->something
от выполнения до value
загрузка завершена. Типичным примером является то, что у вас есть "производитель", который использовал магазин v != 0
пометить что-то другое (pShared->something
) находится в каком-то другом ожидаемом состоянии, например:
pShared->something = 1 ; // was 0
__release_barrier() ;
pShared->value = 1 ; // was 0
В этом типичном потребительском сценарии вам почти всегда будут нужны парные барьеры, один для магазина, который помечает, что вспомогательная память видна (так что эффекты от хранилища значений не видны перед чем-то хранилищем), и один барьер для потребителя (чтобы загрузка чего-либо не началась до завершения загрузки значения).
Эти барьеры также зависят от платформы. Например, на powerpc (с использованием компилятора xlC) вы бы использовали __isync()
а также __lwsync()
для потребителя и производителя соответственно. Какие барьеры необходимы, может также зависеть от механизма, который вы используете для магазина и загрузки value
, Если вы использовали атомарный, который приводит к LOCK
(возможно, неявный), тогда это создаст неявный барьер, поэтому вам может ничего не понадобиться. Кроме того, вам, вероятно, также понадобится разумное использование volatile (или предпочтительно использовать атомарную реализацию, которая делает это под прикрытием), чтобы заставить компилятор делать то, что вы хотите.