переменная и атомарные операции в Visual C ++ x86 - PullRequest
5 голосов
/ 12 февраля 2011

Обычная загрузка имеет семантику получения в x86, обычное хранилище имеет семантику выпуска, однако компилятор все еще может переупорядочивать инструкции. Хотя заборы и заблокированные инструкции (заблокированные xchg, заблокированные cmpxchg) препятствуют переупорядочению как оборудования, так и компилятора, обычные загрузки и хранилища все еще необходимы для защиты с помощью барьеров компилятора. Visual C ++ предоставляет функцию _ReadWriterBarrier (), которая предотвращает переупорядочивание компилятора, также C ++ предоставляет изменяемое ключевое слово по той же причине. Я пишу всю эту информацию только для того, чтобы убедиться, что я все понял правильно. Таким образом, все написанное выше верно, есть ли причина помечать как переменные переменные, которые будут использоваться в функциях, защищенных с помощью _ReadWriteBarrier ()?

Например:

int load(int& var)
{
    _ReadWriteBarrier();
    T value = var;
    _ReadWriteBarrier();
    return value;
}

Безопасно ли делать эту переменную энергонезависимой? Насколько я понимаю, потому что функция защищена и компилятор не может переупорядочить ее. С другой стороны, Visual C ++ обеспечивает специальное поведение для энергозависимых переменных (отличное от стандартного), он делает энергозависимые операции чтения и записи с атомарной загрузкой и хранением, но моя цель - x86, а обычные загрузки и хранилища должны быть атомарными на x86. в любом случае, верно?

Заранее спасибо.

Ответы [ 2 ]

2 голосов
/ 12 февраля 2011

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

Таким образом, это последний сценарий, в котором volatile может рассматриваться как аналог _ReadWriteBarrier.

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

MemoryBarrier предназначен для предотвращения переупорядочения процессора!

Компилятор обычно переставляет инструкции ... C ++ не содержит встроенной поддержки многопоточных программ, поэтому компилятор принимаетКод является однопоточным при переупорядочении кода.В MSVC используйте в коде _ReadWriteBarrier, чтобы компилятор не перемещал операции чтения и записи по нему.

Проверьте эту ссылку для более подробного обсуждения этих тем http://msdn.microsoft.com/en-us/library/ee418650(v=vs.85).aspx

Относительно вашего фрагмента кода - вам не нужно использовать ReadWriteBarrier в качестве примитива SYNC - первый вызов _ReadWriteBarrier не требуется.

При использовании ReadWriteBarrier вам не нужно использовать volatile

Вы написали, что «это делает volatile чтение и запись атомарных загрузок и хранилищ» - я не думаю, что это нормально, говорить это,Атомность и волатильность разные.Атомные операции считаются неделимыми - ... http://www.yoda.arachsys.com/csharp/threads/volatility.shtml

1 голос
/ 12 февраля 2011

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

[править] В общем, я бы полагался на особенности платформы, такие как атомарные чтения x86 и отсутствие OOOX, только в изолированных локальных оптимизациях, которые защищены #ifdef проверкой целевой платформы, в идеале сопровождаемойпортативным решением на пути #else.

На что обращать внимание

  • атомарность операций чтения / записи
  • переупорядочение из-за оптимизаций компилятора (это включает в себя другой видимый порядокдругим потоком из-за простого кеширования регистра)
  • неправильное выполнение в CPU

Возможные заблуждения

1. Насколько я понимаю, поскольку функция защищена и компилятор не может переупорядочить ее внутри.
[edit] Чтобы уточнить: _ReadWriteBarrier обеспечивает защиту от переупорядочения команд, однако вы должны выходить за рамки этой функции._ReadWriteBarrier было исправлено в VS 2010, чтобы сделать это, более ранние версии могут быть сломаны (в зависимости от оптимизации, которую они фактически делают).

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

2. Visual C ++ [...] выполняет нестабильное чтение и запись атомарных загрузок и хранилищ,
Где вы это нашли? MSDN говорит, что за пределами стандарта будут установлены барьеры памяти для чтения и записи, без гарантии атомарного чтения.

[edit] Обратите внимание, что C #, Java, Delphiи т. д. имеют разные значения памяти и могут давать разные гарантии.

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

[edit] Произойдет несогласованное чтение:

char * c = new char[sizeof(int)+1];
load(*(int *)c);      // allowed by standard to be unaligned
load(*(int *)(c+1));  // unaligned with most allocators

#pragma pack(push,1)
struct 
{
   char c;
   int  i;
} foo;
load(foo.i);         // caller said so
#pragma pack(pop)

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

4. Простая загрузка приобрела семантику на x86, обычное хранилищеимеет семантику релиза
Нет. Процессоры x86 не используют неупорядоченное выполнение (или, скорее, никакого видимого OOOX - я думаю), но это не мешает оптимизатору переупорядочивать инструкции.

5. _ReadBarrier / _WriteBarrier / _ReadWriteBarrier делают всю магию, которой они не делают - они просто предотвращают изменение порядка в оптимизаторе.MSDN, наконец, сделал это большим плохим предупреждением для VS2010, но эта информация, очевидно, относится и к предыдущим версиям .


Теперь к вашему вопросу.

Я предполагаю, что цель фрагмента кода - передать любую переменную N и загрузить ее (атомарно?). Простым выбором будет чтение с блокировкой или (в Visual C ++ 2005 и более поздних версиях) энергозависимое чтение.

В противном случае вам потребуется барьер для компилятора и процессора перед чтением, в кабинете VC ++ это будет:

int load(int& var)
{   
  // force Optimizer to complete all memory writes:
  // (Note that this had issues before VC++ 2010)
   _WriteBarrier();    

  // force CPU to settle all pending read/writes, and not to start new ones:
   MemoryBarrier();

   // now, read.
   int value = var;    
   return value;
}

Нет, что _WriteBarrier имеет второе предупреждение в MSDN:* В предыдущих версиях компилятора Visual C ++ функции _ReadWriteBarrier и _WriteBarrier применялись только локально и не влияли на функции в дереве вызовов.Эти функции теперь выполняются вплоть до дерева вызовов. *


I Надеюсь , что правильно.stackoverflowers, пожалуйста, поправьте меня, если я ошибаюсь.

...