Могу ли я установить согласованность кэша на многоядерном процессоре x86? - PullRequest
32 голосов
/ 18 февраля 2009

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

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

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

Ответы [ 9 ]

31 голосов
/ 18 февраля 2009

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 - гораздо лучший способ написания кода без блокировки.

24 голосов
/ 18 февраля 2009

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

14 голосов
/ 18 февраля 2009

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

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

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

6 голосов
/ 18 февраля 2009

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

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

6 голосов
/ 18 февраля 2009

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

3 голосов
/ 14 апреля 2009

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

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

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

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

2 голосов
/ 22 сентября 2009

Следующая статья является хорошей ссылкой на использование volatile с поточными программами.

Изменчивый, почти бесполезный для многопоточного программирования .

1 голос
/ 18 февраля 2009

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

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

...