Всегда ли для спин-блокировок требуется барьер памяти? Дорогое вращение на барьере памяти?

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

Обязательно ли локальное вращение при чтении памяти означает, что я должен ВСЕГДА вставлять барьер памяти перед вращением чтения?

(Чтобы проверить это, мне удалось создать комбинацию считывающего / записывающего устройства, в результате чего читатель никогда не видит записанное значение при определенных очень специфических условиях - выделенный ЦП, процесс, подключенный к ЦП, оптимизатор включен полностью, никакой другой работы выполняется в цикле - поэтому стрелки указывают в этом направлении, но я не совсем уверен в стоимости прокрутки через барьер памяти.)

Какова стоимость прокрутки через барьер памяти, если в буфер хранилища кеша нечего сбрасывать? т.е. все, что делает процесс (в 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 (обратная запись), что означает, что они не удаляются немедленно, но при чтении будет приниматься самое последнее значение (я думаю, они называют это «переадресацией хранилища»). Поэтому я использую инструкцию CAS для писателя. Я обнаружил в руководстве Intel все эти различные типы реализаций записи (UC, WC, WT, WB, WP), Intel vol 3A, главы 11-10, но все еще изучаю их.

Моя неуверенность на стороне читателя: я понимаю из статьи Маккенни, что существует также очередь недействительности, очередь входящих аннулирований из шины в кеш. Я не уверен, как работает эта часть. В частности, вы, кажется, подразумеваете, что цикл через нормальное чтение (то есть без блокировки, без барьера и с использованием volatile только для обеспечения того, чтобы оптимизатор оставил чтение после компиляции), каждый раз будет проверять "очередь недействительности" (если такое существует). Если простого чтения недостаточно (т. Е. Можно прочитать старую строку кеша, которая все еще остается действительной в ожидании признания недействительности в очереди (для меня это тоже звучит немного бессвязно, но как тогда работают очереди недействительности?)), Тогда атомарное чтение будет будет необходимо, и мой вопрос: в таком случае, повлияет ли это на автобус? (Думаю, наверное, нет.)

Я все еще читаю руководство Intel, и, хотя я вижу отличное обсуждение переадресации магазина, я не нашел хорошего обсуждения очередей недействительности. Я решил преобразовать свой код C в ASM и поэкспериментировать, я думаю, что это лучший способ действительно почувствовать, как это работает.


person blais    schedule 25.07.2011    source источник
comment
отлично работает с локальным чтением в большинстве условий. - если не всегда работает нормально, значит, это не нормально .....   -  person Mitch Wheat    schedule 25.07.2011
comment
Что касается теста малого цикла с полной оптимизацией, есть и другие проблемы, например ошибка комы Cyrix (даже если она не применяется в данном случае), что может повлиять на поддельные тесты.   -  person Fred Nurk    schedule 25.07.2011
comment
@ Митч: ну конечно, вот почему я спрашиваю :-)   -  person blais    schedule 25.07.2011
comment
Что вы имеете в виду под местным прядением? чем это отличается от удаленного отжима? что бы было за удаленное вращение?   -  person    schedule 26.07.2011
comment
@Blank: Я использовал терминологию из книги Херлихи / Шавита: (стр. 147). Это понятие локального вращения, когда потоки репрезентативно перечитывают кэшированные значения вместо того, чтобы повторно использовать шину, является важным принципом, имеющим решающее значение для разработки эффективного вращения. замки.   -  person blais    schedule 04.08.2011


Ответы (3)


Команда "xchg reg, [mem]" сигнализирует о намерении заблокировать контакт LOCK ядра. Этот сигнал проходит мимо других ядер и кэшируется до шин управления шиной (варианты PCI и т. Д.), Которые завершают то, что они делают, и, в конечном итоге, вывод LOCKA (подтверждение) будет сигнализировать процессору о том, что xchg может завершиться. Затем сигнал LOCK отключается. Эта последовательность может занять много времени (сотни циклов ЦП и более). Впоследствии соответствующие строки кэша других ядер будут признаны недействительными, и вы получите известное состояние, то есть такое, которое было синхронизировано между ядрами.

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

Имейте в виду, что если блокировка не удалась, вам нужно повторить попытку, выполнив новый xchg.

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

person Olof Forshell    schedule 17.08.2011

Возможно, я неправильно понял вопрос, но ...

Если вы вращаетесь, одна проблема заключается в том, что компилятор оптимизирует ваше вращение. Volatile решает эту проблему.

Барьер памяти, если он у вас есть, будет выдан писателем спин-блокировке, а не считывателем. Писатель на самом деле не должен использовать его - это гарантирует, что запись будет немедленно удалена, но в любом случае она скоро исчезнет.

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

person Community    schedule 26.07.2011

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

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

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

Эти препятствия также зависят от платформы. Например, на powerpc (с использованием компилятора xlC) вы должны использовать __isync() и __lwsync() для потребителя и производителя соответственно. Необходимые барьеры также могут зависеть от механизма, который вы используете для магазина и загрузки value. Если вы использовали атомарную внутреннюю функцию, которая приводит к Intel LOCK (возможно, неявно), тогда это создаст неявный барьер, поэтому вам может ничего не понадобиться. Кроме того, вам, вероятно, также потребуется разумное использование volatile (или предпочтительно использовать атомарную реализацию, которая делает это под покровом), чтобы заставить компилятор делать то, что вы хотите.

person Peeter Joot    schedule 31.07.2013