На большинстве или всех современных процессорах Intel x86 lock cmpxchg
в ячейку с типом памяти WB, полностью содержащуюся в одной строке кэша L1D, выполняется следующим образом:
- L1D выдает запрос чтения с блокировкой, который переводит целевую строку в состояние согласованности кэш-памяти с блокировкой и исключительным доступом и предоставляет запрошенные байты в качестве входных данных для одного из портов выполнения для выполнения сравнения. (Блокировка кэша поддерживается, начиная с P6.) Линия в заблокированном состоянии не может быть аннулирована или исключена по любой причине.
- Проведите сравнение на равенство.
- Каким бы ни был результат, отправьте запрос разблокировки-записи к L1D, который изменяет состояние строки кэша на Modified и разблокирует строку, тем самым позволяя другим запросам доступа или согласованности заменить или сделать строку недействительной.
Первый и последний этапы можно наблюдать эмпирически, используя либо определенные события производительности, либо измерения на основе задержки. Один из способов - выделить большой массив атомарных переменных и затем выполнить lock cmpxchg
в цикле над этим массивом. Тип запроса чтения с блокировкой - это один из типов запросов RFO. Таким образом, событие L2_TRANS.RFO
(или что-то подобное), которое надежно для большинства микроархитектур, можно использовать для измерения количества чтений блокировки в L2. (L2_TRANS.RFO
подсчитывает запросы RFO, поэтому лучше отключить аппаратные средства предварительной выборки, чтобы избежать нежелательных попаданий в L2. Это также относится к L2_RQSTS.RFO_*
.)
Также есть события для измерения количества обратных записей, такие как L2_TRANS.L1D_WB
, L2_TRANS.L2_WB
и другие. К сожалению, многие из этих событий и во многих микроархитектурах либо занижены, либо превышены, либо подсчитываются точно, но не обязательно все / только обратные записи строк грязного кэша. Так что с ними труднее рассуждать, и в целом они ненадежны.
Лучшим способом было бы выполнить lock cmpxchg
в одном разделе массива на определенном физическом ядре, затем перенести поток на другое физическое ядро (в том же домене совместного использования L3) и выполнить цикл, в котором считываются элементы этого раздела ( нормально читает). Если инструкция lock cmpxchg
переводит целевую строку в состояние M, запрос на чтение из другого физического ядра в том же домене совместного использования L3 должен попасть в L3, а также изменен в частных кэшах ядра, на котором было выполнено lock cmpxchg
. Эти события можно подсчитать с помощью OFFCORE_RESPONSE.DEMAND_DATA_RD.L3_HIT.HITM_OTHER_CORE
(или аналогичного), что надежно для большинства / всех микроархитектур.
Заблокированная инструкция - дорогостоящая операция по трем причинам: (1) требует перевода строки в исключительное состояние, (2) делает строку грязной (возможно, излишне) и слишком большое количество обратных записей может существенно повлиять на время выполнения, тем более когда они в конечном итоге крадут полосу пропускания основной памяти из-за длинных отрезков запросов на чтение, и тем более, когда записи производятся в постоянную память, и (3) они архитектурно сериализуются, что делает инструкцию по критическому пути.
У Intel есть патент, который предлагает оптимизацию для последнего, причем ядро оптимистично предполагает, что есть не вызывает конкуренции за блокировку и вызывает спекулятивную нормальную нагрузку на целевую линию. Если линия отсутствует в каком-либо другом физическом ядре, линия будет в исключительном состоянии в запрашивающем ядре. Затем, когда заблокированная инструкция выполняется и выдает запрос чтения блокировки, линия, будем надеяться, все еще будет в исключительном состоянии, и в этом случае общая задержка заблокированной инструкции будет уменьшена. Я не знаю, реализует ли какой-либо процессор эту оптимизацию. Если бы это было реализовано, количество L2_TRANS.RFO
событий было бы намного меньше, чем количество заблокированных строк.
person
Hadi Brais
schedule
11.08.2020
lock cmpxchg
. По крайней мере, исторически (для видимых извне эффектов) felixcloutier.com/x86/cmpxchg говорит: i> Процессор никогда не производит заблокированное чтение без блокированной записи. Но это не исключает оптимизацию блокировки кэш-памяти для кэшируемой памяти в современных ЦП. - person Peter Cordes   schedule 21.07.2020lock cmpxchg
, и вот откуда берется стоимость при вращении на нем вместо вращения только для чтения, пока не станет похоже, что блокировка доступна. Переменная блокировки уже обычно будет грязной (не синхронизированной с DRAM) - person Peter Cordes   schedule 21.07.2020lock bts
. Это явно лучше, потому что это оставляет строку в состоянии S, а не E, и является (или должно быть) хорошо известным фактом среди разработчиков блокировки и других циклов вращения (наравне с использованиемpause
в части повторных попыток вращения). например Примером может служить блокировки манипулирования памятью с помощью встроенной сборки. - person Peter Cordes   schedule 21.07.2020lock cmpxchg
сбой, загрязняющий строку кеша. Я обнаружил стоимость атомарной операции, что делает интересный момент, что чистая загрузка + CAS может вызвать 2 промаха кеша: один для получения общего состояния для нагрузка, еще один, чтобы получить Эксклюзив. Я все еще почти уверен, что вращение только для чтения сpause
после того, как я увидел, что он заблокирован, - хорошая идея, но я не совсем уверен, что чистая загрузка в качестве первой операции - хорошая идея. Чтобы ускорить рассмотрение дела, лучше всего начать с блокировки CAS. - person Peter Cordes   schedule 21.07.2020try_lock
в сообщении, на которое вы ссылаетесь.if(load() == already_locked) goto read-only-spin-loop
перед попыткой первого xchg или CAS. Блокировка манипуляций с памятью с помощью встроенной сборки, которую я связал ранее, написана таким образом (синтаксис NASM). - person Peter Cordes   schedule 22.07.2020xchg
, а затем ослабить нагрузку. - person spongebob   schedule 22.07.2020