В Windows есть три встроенные функции компилятора для реализации барьера памяти:
1. _ReadBarrier;
2. _WriteBarrier;
3. _ReadWriteBarrier;
Однако я обнаружил странную проблему: _ReadBarrier кажется фиктивной функцией, которая ничего не делает! Ниже приведен мой ассемблерный код, сгенерированный VC ++ 2012.
Мой вопрос: как реализовать функцию барьера памяти в инструкциях по сборке?
int main()
{
013EEE10 push ebp
013EEE11 mov ebp,esp
013EEE13 sub esp,0CCh
013EEE19 push ebx
013EEE1A push esi
013EEE1B push edi
013EEE1C lea edi,[ebp-0CCh]
013EEE22 mov ecx,33h
013EEE27 mov eax,0CCCCCCCCh
013EEE2C rep stos dword ptr es:[edi]
int n = 0;
013EEE2E mov dword ptr [n],0
n = n + 1;
013EEE35 mov eax,dword ptr [n]
013EEE38 add eax,1
013EEE3B mov dword ptr [n],eax
_ReadBarrier();
n = n + 1;
013EEE3E mov eax,dword ptr [n]
013EEE41 add eax,1
013EEE44 mov dword ptr [n],eax
}
013EEE56 xor eax,eax
013EEE58 pop edi
013EEE59 pop esi
013EEE5A pop ebx
013EEE5B add esp,0CCh
013EEE61 cmp ebp,esp
013EEE63 call __RTC_CheckEsp (013EC3B0h)
013EEE68 mov esp,ebp
013EEE6A pop ebp
013EEE6B ret
_ReadBarrier
, _WriteBarrier
, а также _ReadWriteBarrier
являются особенности, которые влияют на то, как компилятор может изменить порядок кода; они не имеют абсолютно никакого отношения к барьерам памяти ЦП и действительны только для определенных видов памяти (см. «Затрагиваемая память»). Вот).
MemoryBarrier()
это свойство, которое вы используете для создания барьера памяти процессора. Тем не менее, рекомендация от Microsoft заключается в использовании std::atomic<T>
идти вперед с VC ++.
Современные процессоры способны выполнять инструкции намного раньше, чем они фактически «завершают» инструкции, поэтому используются барьеры памяти, чтобы помешать их выполнению далеко вперед, когда дело доходит до определенных типов операций с памятью, где строгое упорядочение требуется — в большинстве случаев не имеет значения, пишете ли вы в переменную a перед переменной b или b перед переменной a. Но иногда это так.
Набор команд x86 имеет lfence
, sfence
а также fence
, которые являются инструкциями, которые «забивают» загружает, хранит и все операции с памятью соответственно. Дело в том, что инструкция «забор» или «барьер» заключается в обеспечивать что все инструкции, предшествующие инструкции барьера, завершили свою загрузку, сохраняют или обе перед следующей инструкцией после продолжения барьера.
Это важно, если вы реализуете, например, семафоры, мьютексы или аналогичные инструкции, так как важно сохранить значение, говорящее «Я заблокировал семафор», прежде чем вы, например, продолжите читать другие данные. В противном случае все может пойти не так, скажем так.
Обратите внимание, что если вы ДЕЙСТВИТЕЛЬНО не знаете, что вы делаете с барьерами памяти, вероятно, лучше НЕ использовать их — и полагаться на уже существующий код, который решает ту же проблему — std::atomic
одно место для финансирования такого кода. Я написал довольно сложный код, но только один или два раза мне понадобился барьер памяти в моем коде.
Несколько раз мне нужно было заставить компилятор не распространять код, что можно сделать с помощью «неоперативных функций», и, очевидно, в наши дни для этого есть даже специальные встроенные функции.
Есть несколько важных моментов для рассмотрения. Возможно,
Во-первых, барьеры имеют эффект только в многопоточных
код, и большинство компиляторов требуют специальной опции для создания
многопоточный код. И тому подобное _ReadBarrier
почти
конечно встроенные компиляторы, и должен ничего не делать, если
Вы дали опции для многопоточного кода.
Второе — это то, что требуется для оборудования, даже в
многопоточный контекст, меняется. На большинстве машин я
работал на (более сорока лет), машина никогда не требовалась
что-нибудь; барьеры становятся актуальными, только если машина имеет
сложные чтения и записи конвейеров. (Самые ранние машины
не было даже инструкций по забору или барьеру, поэтому сгенерированный
код должен быть пустым.)