Статья позволяет понять протокол когерентности кэша процессора MESI.

Java

Кэш процессора (кэш-память)

Почему процессоры имеют кэши?

Процессоры удваиваются каждые 18 месяцев в соответствии с законом Мура, но память и жесткие диски развиты гораздо меньше, чем процессоры. Это делает высокопроизводительную память и жесткие диски чрезвычайно дорогими. Однако высокоскоростные операции ЦП требуют высокоскоростных данных. Чтобы решить эту проблему, производители ЦП встроили в ЦП небольшой объем кэш-памяти, чтобы устранить несоответствие между скоростью ввода-вывода и скоростью работы ЦП.
Когда ЦП обращается к устройству хранения, независимо от того, обращается ли он к данным или к инструкциям, он имеет тенденцию собираться в непрерывной области, что называется принципом локальности.
Временная местность:Если к элементу информации осуществляется доступ, вероятно, к нему снова будут обращаться в ближайшем будущем. Такие как циклы, рекурсия, повторные вызовы методов и т. д.
Пространственная местность:Если имеется ссылка на место памяти, то будущие места рядом с ним также будут упоминаться. Например, последовательно выполняемый код, два последовательно созданных объекта, массивы и т. д.

Поток вычислений, выполняемых ЦП с кешем

  1. Программа и данные загружаются в оперативную память
  2. Инструкции и данные загружаются в кэш ЦП.
  3. Процессор выполняет инструкцию и записывает результат в кеш.
  4. Данные в кеше записываются обратно в основную память

Текущая популярная многоуровневая структура кэша

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

Многоядерный процессор, многоуровневый протокол когерентности кеша MESI

В случае многоядерного процессора есть несколько кешей первого уровня.Как обеспечить согласованность внутренних данных кеша и предотвратить загромождение системных данных. Это приводит к последовательному протоколу MESI.

Статус кэша протокола MESI

MESI относится к инициалам штатов в 4. Каждая строка кэша имеет 4 состояния, которые могут быть представлены двумя битами:
Строка кэша: модуль, в котором кэш хранит данные.

Уведомление:
Он всегда точен для состояний M и E, которые согласуются с истинным состоянием строки кэша, в то время как состояние S может быть несогласованным. Если один кеш делает недействительной строку кеша в состоянии S, другой кеш может фактически иметь исключительное использование строки кеша, но кеш не будет переводить строку кеша в состояние E, потому что другие кеши не будут широковещательно передавать свое уведомление об аннулировании кеша. строки, а также потому, что кеш не хранит количество копий строки кеша, поэтому (даже если есть такое уведомление) нет возможности определить, имеет ли он эксклюзивное использование строки кеша.
В приведенном выше смысле состояние E является спекулятивной оптимизацией: если ЦП хочет изменить строку кэша в состоянии S, шинная транзакция должна перевести все копии строки кэша в недопустимое состояние и изменить кэш Состояние E. Нет необходимости использовать транзакции шины.

переход состояния МЭСИ


Поймите преамбулу рисунка:
1. Триггерные события

2. классификация кеша:
Предпосылка: все кэши совместно кэшируют определенный фрагмент данных в основной памяти.
Локальный кеш: относится к кешу текущего процессора.
Кэш триггера: кеш, который запускает события чтения и записи.
Другие кеши: Относится к кешам, отличным от двух указанных выше.
Примечание. Локальное событие запускает локальный кеш, и кеш триггера одинаков.
Объяснение переключения на приведенном выше рисунке:

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

Возьмем, к примеру, каштан:
Предположим, что в кэше 1 есть строка кэша с переменной x = 0 в состоянии S (общая).
Затем другие строки кэша кэша 2, кэша 3 и т. д. с переменной x переводятся в состояние S (совместно используемое) или в состояние I (недействительное).

Взаимодействие многоядерного кэша

Предположим, что имеется три процессора A, B и C, и соответствующие им три кэша — это кэш a, b и c. Эталонное значение x определяется как 0 в основной памяти.

одноядерное чтение

Тогда поток выполнения:
CPU A выдает команду прочитать x из основной памяти.
Чтение из основной памяти в кэш по шине (удаленное чтение), это строка Cache, модифицированная до состояния E (эксклюзивное).

Двухъядерное чтение

Тогда поток выполнения:
CPU A выдает команду прочитать x из основной памяти.
CPU A читает из основной памяти в кэш через шину и устанавливает строку кэша в состояние E.
CPU B выдает команду прочитать x из основной памяти.
CPU B пытается прочитать x из основной памяти, CPU A обнаруживает конфликт адресов. В этот момент CPU A отвечает на соответствующие данные. В это время X хранится в Cache A и Cache B, а X устанавливается в состояние S в ChChe A и Cache B (общий).

изменить данные

Тогда поток выполнения:
После того, как ЦП А завершит вычисление, ему необходимо изменить x.
ЦП A устанавливает x в состояние M (изменено) и информирует ЦП B о кэшировании x, ЦП B устанавливает x в локальном кэше b в состояние I (недействительно)
ЦП А назначает x.

Синхронные данные

Тогда поток выполнения:
ЦП B выдал команду на чтение x.
ЦП B уведомляет ЦП A, и когда ЦП A синхронизирует измененные данные с основной памятью, кэш a изменяется на E (эксклюзивный)
ЦП A синхронизирует x ЦП B и устанавливает x в кеше a и кеше b после синхронизации в состояние S (общий).

Оптимизации МЭСИ и связанные с ними проблемы

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


Решение для блокировки состояния переключения ЦП - Store Buffers

Например, если вам нужно изменить часть информации в локальном кеше, вы должны уведомить о статусе I (недействительно) другие кеши ЦП, в которых есть кэшированные данные, и дождаться подтверждения. Процесс ожидания подтверждения блокирует процессор, что снижает производительность процессора. Это должно быть потому, что это ожидание намного дольше, чем время выполнения инструкции.


Store Bufferes

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


Риски буферов магазина

Во-первых, процессор попытается прочитать значение из буфера хранилища, но оно еще не зафиксировано. Решение для этого называется Store Forwarding, которое возвращает загрузку, если она существует в кэше хранилища.
Во-вторых, нет никакой гарантии, когда сохранение будет завершено.
alue = 3;
void exeToCPUA(){
  value = 10;
  isFinsh = true;
}
void exeToCPUB(){
  if(isFinsh){
    //value一定等于10?!
    assert value == 10;
  }
}
Представьте себе, что при выполнении CPU A сохраняет Finished в e (эксклюзивном) состоянии, а Value не сохраняется в его кеше. (например, недействительный). В этом случае Value откажется от кеша хранилища, чем Finished. Вполне возможно, что CPU B считывает значение true для Finished, а значение Value не равно 10.
То есть присваивание isFinsh предшествует присваиванию значения.
Такие изменения в идентифицируемом поведении называются переупорядочиванием. Обратите внимание, что это не означает, что расположение вашей директивы было изменено злонамеренно (или из лучших побуждений).
Это просто означает, что другие процессоры будут считывать результаты не в том порядке, в котором они были записаны в программе.
Кстати, дизайн NIO очень похож на дизайн Store Buffers.


аппаратная модель памяти

Аннулирование выполнения — непростая операция, для ее обработки требуется процессор. Кроме того, Store Buffers не бесконечны, поэтому процессору иногда приходится ждать возврата подтверждения недействительности. Обе эти операции могут значительно снизить производительность. Чтобы справиться с этой ситуацией, вводится очередь инвалидации. Их соглашения таковы:
  • Для всех входящих запросов Invalidate сообщение Invalidate Acnowlege ДОЛЖНО быть отправлено немедленно.
  • Invalidate на самом деле не выполняется, а ставится в специальную очередь и выполняется, когда это удобно.
  • Процессор не будет отправлять никаких сообщений в обработанную запись кэша, пока не обработает Invalidate.
Даже тогда процессор не знает, когда оптимизации разрешены, а когда нет.
Процессор просто оставляет задачу тому, кто написал код. Это барьер памяти (Memory Barriers).

Барьер записи Барьер сохранения памяти (также известный как ST, SMB, smp_wmb) — это инструкция, которая сообщает процессору применить все сохранения, уже находящиеся в буфере сохранения, перед выполнением следующей за ней инструкции.
Барьер чтения Барьер загрузки памяти (также известный как LD, RMB, smp_rmb) — это инструкция, которая говорит процессору применить все операции аннулирования, уже находящиеся в очереди аннулирования, перед выполнением каких-либо загрузок.
void executedOnCpu0() {
    value = 10;
    //在更新数据之前必须将所有存储缓存(store buffer)中的指令执行完毕。
    storeMemoryBarrier();
    finished = true;
}
void executedOnCpu1() {
    while(!finished);
    //在读取之前将所有失效队列中关于该数据的指令执行完毕。
    loadMemoryBarrier();
    assert value == 10;
}

наконец

Приглашаю всех обратить внимание на мой Gong Zonghao [Программист в погоне за ветром]. Я собрал 1000 вопросов для интервью по Java от нескольких компаний в 2019 году и более 400 страниц документов в формате pdf. Статьи будут обновляться в нем, а сопоставленные материалы будут также помещаться в него. Если вам понравилась статья, не забудьте поставить лайк, спасибо за вашу поддержку!