Могу ли я принудительно поддерживать согласованность кэша на многоядерном процессоре x86?

StackOverflow https://stackoverflow.com/questions/558848

Вопрос

На прошлой неделе я написал небольшой класс thread и канал односторонних сообщений, чтобы обеспечить связь между потоками (очевидно, по два канала на поток для двунаправленной связи).На моем Athlon 64 X2 все работало нормально, но мне было интересно, не столкнусь ли я с какими-либо проблемами, если оба потока просматривают одну и ту же переменную, а локальное кэшированное значение для этой переменной на каждом ядре не синхронизировано.

Я знаю, что изменчивый ключевое слово заставит переменную обновляться из памяти, но есть ли способ на многоядерных процессорах x86 принудительно синхронизировать кэши всех ядер?Это то, о чем мне нужно беспокоиться, или изменчивый и правильное использование облегченных механизмов блокировки (я использовал _InterlockedExchange для установки моих изменчивых переменных канала) обрабатывает все случаи, когда я хочу написать код без блокировки для многоядерных процессоров x86?

Я уже знаю и использовал Критические разделы, Мьютексы, События и так далее.В основном мне интересно, существуют ли встроенные функции x86, о которых я не знаю, какая сила или которые могут быть использованы для обеспечения согласованности кэша.

Это было полезно?

Решение

volatile только заставляет ваш код перечитывать значение, он не может контролировать, откуда считывается значение.Если значение было недавно прочитано вашим кодом, то оно, вероятно, будет в кэше, и в этом случае volatile заставит его повторно прочитать из кэша, А НЕ из памяти.

В x86 не так много инструкций по согласованности кэша.Существуют инструкции предварительной выборки, такие как prefetchnta, но это не влияет на семантику упорядочения памяти.Раньше это реализовывалось путем переноса значения в кэш L1 без загрязнения кэша L2, но для современных разработок Intel с большим общим инклюзивный Кэш L3.

процессоры x86 используют вариацию на Протокол MESI (MESIF для Intel, MOESI для AMD), чтобы поддерживать согласованность их кэшей друг с другом (включая частные кэши L1 разных ядер).Ядро, которое хочет записать строку кэша, должно заставить другие ядра аннулировать свою копию этой строки, прежде чем оно сможет перевести свою собственную копию из общего состояния в Измененное.


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

Вам действительно нужно предотвратить изменение порядка во время компиляции, потому что модель памяти C ++ слабо упорядочена. volatile это старый, плохой способ сделать это;C ++ 11 std::atomic - гораздо лучший способ написания кода без блокировок.

Другие советы

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

Вам не нужно беспокоиться о согласованности кэша.Аппаратное обеспечение позаботится об этом.О чем вам, возможно, придется беспокоиться, так это о проблемах с производительностью из-за такой согласованности кэша.

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

Поскольку целая строка кэша (64 байта) должна быть считана из памяти (или записана обратно в общий кэш, а затем прочитана ядром # 2), это приведет к некоторым потерям производительности.В данном случае это неизбежно.Это желаемое поведение.


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

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

Volatile этого не сделает.В C ++ значение volatile влияет только на то, какие оптимизации выполняет компилятор, такие как сохранение переменной в регистре вместо памяти или ее полное удаление.

Вы не указали, какой компилятор вы используете, но если вы работаете в Windows, взгляните на эта статья здесь.Также взгляните на доступныездесь функционирует синхронизация.Возможно, вы захотите отметить, что в целом volatile этого недостаточно, чтобы делать то, что вы хотите, но в VC 2005 и 2008 к нему добавлена нестандартная семантика, которая добавляет подразумеваемые барьеры памяти вокруг чтения и записи.

Если вы хотите, чтобы все было портативным, вам предстоит гораздо более трудный путь.

Есть серия статей, объясняющих современные архитектуры памяти здесь, включая Кэши Intel Core2 и многие другие темы современной архитектуры.

Статьи очень удобочитаемы и хорошо иллюстрированы.Наслаждайтесь !

В вашем вопросе есть несколько подвопросов, поэтому я отвечу на них, насколько мне известно.

  1. В настоящее время в C ++ не существует переносимого способа реализации взаимодействий без блокировки.Предложение C ++ 0x решает эту проблему путем введения библиотеки atomics.
  2. Volatile не гарантирует атомарность на многоядерном сервере, и его реализация зависит от конкретного поставщика.
  3. На x86 вам не нужно делать ничего особенного, за исключением объявления общих переменных как volatile, чтобы предотвратить некоторые оптимизации компилятора, которые могут нарушить работу многопоточного кода.Volatile указывает компилятору не кэшировать значения.
  4. Существуют некоторые алгоритмы (например, Dekker), которые не будут работать даже на x86 с изменчивыми переменными.
  5. Если вы не знаете наверняка, что передача доступа к данным между потоками является основным узким местом производительности в вашей программе, держитесь подальше от решений без блокировок.Используйте передачу данных по значению или блокировкам.

Ниже приведена хорошая статья, касающаяся использования volatile с потоковыми программами.

Volatile Почти Бесполезен для многопоточного программирования.

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

Редактировать:Если вы используете компилятор Intel или GCC, вы можете использовать атомные встроенные элементы, которые, похоже, делают все возможное, чтобы вытеснить кэш, когда это возможно.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top