Спин-замки всегда требуют барьер памяти? Дорого ли крутится барьер памяти? - PullRequest
8 голосов
/ 25 июля 2011

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

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

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

Какова стоимость вращениячерез барьер памяти, если нечего очищать в буфере хранилища кеша?т. е. весь процесс, выполняемый (в 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:

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

Я думаю, что по умолчанию простые записи - это записи WB (Write Back), что означает, что они не сбрасываются немедленно, но чтения принимают свое последнее значение (я думаю, они называют это «пересылкой из хранилища»).Поэтому я использую инструкцию CAS для писателя.Я обнаружил в руководстве Intel все эти различные типы реализаций записи (UC, WC, WT, WB, WP), Intel vol. 3A, глава 11-10, все еще узнавая о них.

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

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

Ответы [ 3 ]

4 голосов
/ 17 августа 2011

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

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

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

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

2 голосов
/ 26 июля 2011

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

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

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

0 голосов
/ 31 июля 2013

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

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 (или предпочтительно использовать атомарную реализацию, которая делает это под прикрытием), чтобы заставить компилятор делать то, что вы хотите.

...