Кэш-когерентность
Кэш-когерентность (англ. cache coherence) — это свойство многопроцессорной системы, обеспечивающее согласованность данных, хранящихся в кэш-памяти каждого процессора, с данными в общей оперативной памяти. В системах с несколькими процессорами (или ядрами), каждый из которых имеет собственную кэш-память, возникает проблема: один процессор может изменить значение переменной в своём кэше, в то время как другой процессор продолжает использовать устаревшее значение из своего кэша. Кэш-когерентность гарантирует, что все процессоры видят единое, актуальное состояние памяти для любой ячейки данных.
История и предпосылки
С развитием многопроцессорных архитектур в 1980-х годах проблема когерентности кэша стала критической. Ранние многопроцессорные системы, такие как IBM System/370, использовали простые протоколы, но с ростом числа процессоров и объёмов кэша потребовались более сложные решения. В 1983 году был предложен протокол MESI (Illinois protocol), ставший основой для большинства современных систем. К началу 1990-х годов, с появлением многоядерных процессоров, кэш-когерентность стала неотъемлемой частью архитектуры x86 (начиная с Intel Pentium Pro) и ARM.
Проблема когерентности
Проблема возникает из-за наличия нескольких копий одних и тех же данных: в основной памяти и в кэшах каждого процессора. Без механизма когерентности возможны три классических сценария:
- Сценарий чтения-записи: процессор A записывает новое значение в ячейку X, но процессор B читает устаревшую копию из своего кэша.
- Сценарий записи-записи: два процессора одновременно записывают разные значения в одну ячейку, и результат не определён.
- Сценарий миграции данных: процессор A перемещает данные в свой кэш, изменяет их, а затем процессор B пытается прочитать исходные данные из памяти.
Для решения этих проблем разработаны протоколы когерентности, которые следят за состоянием кэш-линий и координируют доступ.
Протоколы кэш-когерентности
Все протоколы можно разделить на два основных класса: снуп-протоколы (snooping) и каталоговые протоколы (directory-based). Внутри каждого класса существует множество вариаций.
Снуп-протоколы
Снуп-протоколы работают на общей шине (или кольцевой топологии), где каждый кэш-контроллер «подслушивает» (snoops) все транзакции на шине. Если один процессор выполняет запись, он отправляет широковещательное сообщение, и все остальные кэши проверяют, не содержат ли они копию изменяемой ячейки. Если да — они либо аннулируют (invalidate) свою копию, либо обновляют её (update). Основные протоколы:
- MSI (Modified, Shared, Invalid) — базовый протокол с тремя состояниями: модифицировано (только в этом кэше и изменено), разделяемо (копии есть в других кэшах, чисто) и недействительно (копия устарела).
- MESI (Modified, Exclusive, Shared, Invalid) — расширение MSI, добавляющее состояние Exclusive (копия только в этом кэше, но не изменена). Это позволяет избежать лишнего широковещания при записи в эксклюзивную линию.
- MOESI (Modified, Owned, Exclusive, Shared, Invalid) — добавляет состояние Owned (модифицировано, но другие кэши могут иметь копию; владелец отвечает за запись обратно в память). Используется в процессорах AMD.
- MESIF (Modified, Exclusive, Shared, Invalid, Forward) — вариант Intel, где состояние Forward позволяет одному кэшу отвечать на запросы чтения, не обращаясь к памяти.
Каталоговые протоколы
В системах с большим числом процессоров (например, в NUMA-архитектурах) широковещание на шине становится неэффективным. Каталоговые протоколы используют централизованный или распределённый каталог, который хранит информацию о том, какие кэши содержат копии каждой кэш-линии. При записи контроллер обращается к каталогу, чтобы отправить инвалидацию только нужным кэшам, а не всем. Примеры: протоколы для SGI Origin 2000, IBM Power4, современных процессоров Intel Xeon (с использованием кольцевой шины и распределённых каталогов).
Аппаратная реализация
Кэш-когерентность реализуется на уровне кэш-контроллера и межсоединений. В современных многоядерных процессорах (например, Intel Core i7, AMD Ryzen) используется комбинация подходов: внутри кристалла — снуп-протоколы на кольцевой или сетчатой топологии, между кристаллами (в многокристальных системах) — каталоговые протоколы. Ключевые компоненты:
- Кэш-контроллер — управляет состояниями кэш-линий и обрабатывает протокольные сообщения.
- Межсоединение — шина, кольцо или сетка (mesh), по которой передаются сообщения (запросы чтения, инвалидации, ответы).
- Буферы — для хранения ожидающих транзакций (например, write buffer, invalidation queue).
Влияние на производительность
Кэш-когерентность вносит накладные расходы:
- Задержка — при записи процессор может ждать подтверждения инвалидации от других кэшей.
- Трафик — широковещательные сообщения загружают шину (особенно в снуп-протоколах).
- Сложность — увеличение числа транзисторов и энергопотребления.
Для снижения накладных расходов применяются оптимизации: использование состояний Exclusive/Owned, предварительная выборка, буферизация инвалидаций, а также когерентность на уровне кэш-линии (64 байта в x86), а не отдельных байтов.
Программная когерентность
В некоторых системах (например, в графических процессорах или встраиваемых системах) кэш-когерентность может обеспечиваться программно. Программист или компилятор вставляет барьеры памяти (memory barriers) и инструкции сброса кэша (например, clflush в x86). Этот подход дешевле аппаратно, но требует аккуратности и снижает производительность при частых синхронизациях. Примеры: OpenCL, CUDA (частично), некоторые DSP.
Примеры в архитектурах
- x86 (Intel/AMD) — полная аппаратная когерентность на уровне кэшей L1, L2, L3. Используются протоколы MESI/MOESI/MESIF. В многокристальных системах (например, Intel Xeon с несколькими кристаллами) — каталоговый протокол с распределённым каталогом.
- ARM — в многоядерных процессорах (Cortex-A серии) — снуп-протоколы (MESI с поддержкой ACE — Advanced Coherency Extensions). В big.LITTLE — когерентность между кластерами через шину CCI (Cache Coherent Interconnect).
- RISC-V — стандарт не фиксирует протокол, но в реализациях (например, SiFive) используется MESI или MOESI.
- GPU — в NVIDIA (начиная с архитектуры Pascal) и AMD (Vega) — частичная когерентность между GPU и CPU через унифицированную память (HMM — Heterogeneous Memory Management).
Критика и альтернативы
Основная критика аппаратной когерентности — сложность и энергопотребление, особенно в системах с десятками и сотнями ядер. Альтернативные подходы:
- Когерентность на уровне транзакционной памяти — аппаратная поддержка атомарных блоков, которые автоматически управляют когерентностью.
- Модели памяти без когерентности — в некоторых встраиваемых системах (например, Texas Instruments C66x) программист сам управляет кэшем.
- Распределённые разделяемые памяти — программные системы (DSM — Distributed Shared Memory), которые эмулируют когерентность на уровне ОС.
Интересные факты
- Первый коммерческий процессор с аппаратной когерентностью кэша — Intel 80386 (1985), но только в многопроцессорных конфигурациях с внешним контроллером.
- В суперкомпьютере Cray X-MP (1982) использовалась программная когерентность через барьеры.
- Протокол MESI был разработан в Университете Иллинойса в 1983 году и до сих пор является основой для большинства CPU.
Источники
- Hennessy, J. L., Patterson, D. A. «Computer Architecture: A Quantitative Approach» (6th edition), 2019.
- Intel Corporation. «Intel 64 and IA-32 Architectures Software Developer’s Manual», Volume 3A, Chapter 11.
- AMD. «AMD64 Architecture Programmer’s Manual», Volume 2, Chapter 7.
- Sorin, D. J., Hill, M. D., Wood, D. A. «A Primer on Memory Consistency and Cache Coherence», Morgan & Claypool, 2011.
- ARM Limited. «ARM Architecture Reference Manual ARMv8-A», Chapter B2.
BFOmetr — база данных и аналитика по компаниям России.
На главную BFOmetr →