Когерентность кэша

Опубликовано: 22 Марта, 2023

Введение

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

Что такое когерентность кэша? В контексте многоядерных процессоров когерентность кеша относится к целостности данных, хранящихся в кеше каждого ядра. Но зачем нужна когерентность кэша? Чтобы ответить на этот вопрос, я обращусь к многоядерному процессору, показанному на рисунке 1. Представьте, что через процессор проходят два потока; один в ядре 1 и один в ядре 2. Теперь представьте, что каждое ядро обращается из основной памяти к переменной «x» и помещает эту переменную в свой кэш. Теперь, если ядро 1 изменяет значение переменной «x», тогда значение, которое ядро 2 имеет в своем кеше для переменной «x», не синхронизировано со значением, которое ядро 1 имеет в своем кеше. Это важная проблема для многоядерных процессоров. На самом деле, эта проблема мало чем отличается от проблем когерентности кэша мультипроцессоров (несколько чипов).

Изображение 20275
Рисунок 1: Схема многоядерного процессора (любезно предоставлено csail.mit.edu)

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

Изображение 20276

Рисунок 2: Многоядерный чип с межъядерной шиной (любезно предоставлено csail.mit.edu)

Протоколы когерентности кэша

Существует два основных метода использования межъядерной шины для уведомления других ядер, когда ядро изменяет что-либо в своем кэше. Один из методов называется «обновление». В методе обновления, если ядро 1 изменяет переменную «x», оно отправляет обновленное значение «x» на межъядерную шину. Каждый кеш всегда слушает межъядерную шину; если кеш увидит переменную на шине, копия которой у него есть, он прочитает обновленное значение. Это гарантирует, что все кэши имеют самое актуальное значение переменной.

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

На первый взгляд может показаться, что метод обновления является предпочтительным методом, потому что все мы знаем, что промахи кэша могут привести к значительным потерям производительности. Однако метод обновления вызывает значительный объем трафика на межъядерной шине, поскольку сигнал обновления должен отправляться на шину каждый раз, когда переменная обновляется. Метод аннулирования требует только отправки сигнала аннулирования при первом изменении переменной; вот почему метод аннулирования является предпочтительным методом.

Этот пример метода аннулирования является очень простым. За прошедшие годы было проделано много работы по улучшению производительности когерентности кэша. Это привело к появлению ряда протоколов когерентности кэша.

MSI

MSI — это базовый, но хорошо известный протокол когерентности кэша. MSI расшифровывается как Modified, Shared и Invalid. Это три состояния, в которых может находиться строка кеша. Состояние Modified означает, что переменная в кеше была изменена и, следовательно, имеет значение, отличное от того, что находится в основной памяти; кеш отвечает за запись переменной обратно в основную память. Состояние Shared означает, что переменная существует по крайней мере в одном кэше и не изменяется; кеш может вытеснить переменную, не записывая ее обратно в основную память. Состояние Invalid означает, что значение переменной было изменено другим кешем и это значение недопустимо; кеш должен прочитать новое значение из основной памяти (или другого кеша).

МЕСЯЦЫ

Другим хорошо известным протоколом когерентности кэша является протокол MESI. MESI расшифровывается как Modified, Exclusive, Shared и Invalid. Состояния Modified и Invalid для этого протокола такие же, как и для протокола MSI. Этот протокол вводит новое состояние; Эксклюзивное состояние. Эксклюзивное состояние означает, что переменная находится только в этом кеше и ее значение совпадает со значением в основной памяти. Теперь это означает, что состояние Shared указывает на то, что переменная содержится более чем в одном кэше.

ДЫМ

Протокол MOSI идентичен протоколу MSI, за исключением того, что он добавляет состояние Owned. Состояние «В собственности» означает, что процессор «владеет» переменной и будет предоставлять текущее значение другим кэшам по запросу (или, по крайней мере, он решит, будет ли он предоставлять его по запросу). Это полезно, потому что другому кешу не нужно будет считывать значение из основной памяти, и он получит его из кеша-владельца намного, намного быстрее.

МОЭСИ

Протокол MOESI представляет собой комбинацию протоколов MESI и MOSI.

Когерентность кэша на основе каталогов

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

На рисунках 1 и 2 ядра процессора соединены шиной, это может быть не так, когда количество ядер начинает значительно увеличиваться. Надеюсь, я показал вам, что сам процессор на самом деле представляет собой сеть ядер, очень похожую на сеть компьютеров с различными взаимосвязанными топологиями. Имея это в виду, многие процессоры будут разрабатываться с ядрами, соединенными по кольцевой топологии. Конечно, если мы думаем о процессоре как о сети ядер, помимо топологии возникает ряд проблем, на которые необходимо обратить внимание. Например; прикладные протоколы, сеансы и, в основном, любые темы, которые я обсуждал в своей серии эталонных моделей OSI, применимы к этим обсуждаемым сетям ядер.

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