Чотири Стани Одного Заразу
Кожна лінія кешу MESI знаходиться в одному з чотирьох станів одночасно. Змінений (M) означає, що цей ядро має єдисну копію в кеші, вона була змінена з моменту завантаження та її значення відрізняється від основної пам’яті, тому цей кеш тепер відповідає за врешті-решт її запис назад. Виключний (E) означає, що це ядро знову має єдисну копію, але вона не була змінена, і вона точно відповідає пам'яті, що означає, що ядро може безшумно оновити її до Зміненого пізніше без будь-якого трафіку по шині, якщо воно записує. Поділений (S) означає, що потенційно кілька ядер мають одночасно дійсну, незмінну копію рядка, що безпечно для читання, але означає, що ядро повинно сповістити всіх інших перед тим, як воно може записати. Недійсний (I) означає, що цей кеш ніколи не мав рядка або його копія була визнана іншим ядром, тому будь-який доступ потрібно повернутися для отримання свіжих даних. Розумним є те, що Виключний і Поділений обидва представляють чисті, незмінені дані, але Виключний повідомляє апаратному забезпеченню, що ніхто інший не має копії, дозволяючи дешевий безшумний перехід до Зміненого, тоді як Поділений змушує здійснити явне трансляцію визнання недійсністю спочатку. Ця відмінність сама по собі зменшує значну кількість непотрібного трафіку по шині для даних, які читаються одним ядром і негайно записуються тим самим ядром, дуже поширений шаблон у реальних програмах.
Слідкування за шиною: як ядра дізнаються про одне одного
MESI є протоколом слідкування, що означає, що кожен кеш спостерігає або слідкує за транзакціями, виданими іншими ядрами, через спільну шину, замість того, щоб покладатися на центральну директорію для відстеження володіння. Коли ядро A хоче прочитати адресу, яку не має кешованою, воно випускає запит на читання шини. Будь-який інший кеш слідкує за цим запитом і перевіряє, чи має він рядок; якщо один є та він модифікований, цей кеш повинен постачати дані безпосередньо, записати їх назад у пам’ять і перейти на спільне володіння, а нова копія ядра A також стане спільною. Якщо іншого кешу немає, ядро A може завантажити його як виключне, оскільки тепер воно єдиним власником. Якщо ядро A замість цього хоче записати, воно випускає запит на читання-для-власності шини (іноді називається «оновленням» шини, якщо воно вже має спільну копію), який слідкують усі інші кеші та відповідають йому шляхом недійсності своїх власних копій цього рядка, переходячи на недійсно. Лише після того, як всі інші кеші підтверджують недійсність, рядок ядра A стає модифікованим, гарантуючи, що тепер воно є єдиним, авторитетним копією. Цей постійний діалог, запити, які передаються та будь-який контролер кешу перевіряє та реагує на них, і це механізм, який робить ілюзію однієї послідовної спільної пам’яті можливою між фізично окремими кешами.
Чому Когерентність Вартість Продуктивності: Неправильне Обмінне Резонування та Шторми Інвалідності
Трафік когерентності не безкоштовний, і два патерни яскраво демонструють його вартість. По-перше, коли кілька ядер повторно записують в одну й ту саму адресу, кожен запис змушує всі інші кешовані копії недійснюватись, таким чином лінія
$ping-pong
$поміж
ядрами
кожний
запис
накладає
повний
маршрут
інвалідності
перед
тим
що
він
може
продовжуватись
це
патерн
часто
називають
штормом
інвалідності
або
неправильним
обмінним
резонуванням”. По-друге, і ще більш підступно, відбувається неправильне обмінне резонування, коли дві незалежні змінні, які ніколи не мають логічного зв’язку, випадково сидять в одній кеш-лінії, зазвичай 64 байти на більшості сучасних процесорів. Незважаючи на те, що ядро A торкається лише змінної X, а ядро B – лише змінної Y, оскільки X і Y діляться лінією, кожен запис будь-яким з ядер змушує всю лінію, включаючи змінну іншого ядра, недійснюватись та перезавантажуватись, викликаючи трафік когерентності, який нічого не має спільного з будь-якою фактичною залежністю даних у програмі. Це може безшумно погіршити багатопотокову продуктивність вдесятьох разів без жодних неправильних поведінок, що робить його надзвичайно важко діагностувати без інструментів вимірювання продуктивності обладнання, які конкретно відстежують події когерентності кешу. Додавання заповнювачів структурам даних таким чином, щоб лічильники або блоки для кожного потоку опинились на окремих кеш-лініях, є стандартним, хоч і неоптимальним у плані використання пам’яті, рішенням саме цієї проблеми, і це один із найпоширеніших реальних уроків, які механіка MESI вчать безпосередньо.
Приклад із використанням двох ядер, одного лічильника
Розглянемо два ядра, обидва з яких хочуть збільшити спільний лічильник за адресою X, починаючи з Invalid всюди, де X дорівнює нулю в пам’яті. Ядро 1 читає X: жодного кешу для нього немає, тому воно отримує дані з пам'яті та завантажує рядок як Exclusive. Ядро 1 потім збільшує і записує; оскільки воно вже має Exclusive, не потрібна транзакція по шині, воно тихо переводить рядок у Modified із новою цінністю, це швидкий шлях, який Exclusive дозволяє використовувати. Тепер ядро 2 читає X: воно надсилає запит на зчитування по шині, ядро 1 прослуховує це, бачить, що воно Modified, і постачає ядру 2 поточну цінність, записуючи при цьому дані назад у пам’ять; обидва кеші тепер містять рядок як Shared. Ядро 2 потім хоче збільшити і записати: оскільки його копія лише Shared, воно повинно випустити запит на читання для отримання права власності або підвищити рівень, який ядро 1 прослуховує та відповідає шляхом недійсності своєї власної копії, переходячи в Invalid; тільки тоді рядок ядра 2 стає Modified із збільшеною цінністю. Якщо ядро 1 негайно знову читає X, воно повертається до Invalid, що викликає ще одну транзакцію по шині та черговий обмін даними. Зверніть увагу, що наївний чергування збільшення між двома ядрами перетворює те, що здається простим арифметичним обчисленням, на безперервний потік транзакцій по шині, кожна з яких несе реальну затримку, і саме тому контарі та атомарні збільшення так ретельно проєктуються, а інструменти профілювання продуктивності обладнання повідомляють про зупинки узгодження кешу як окрему, часто домінуючу категорію навантажень у багатопотоковому коді.”]} p1 =
paragraphs2
Нащадки MESI та їх вплив у реальному світі
MESI, як описано, є основою, але виробничі процесори розширюють її. MOESI, який використовується Intel, додає стан «Провидця», щоб серед кількох спільних копій точно один кеш відповідав за передачу даних майбутньому запитувачу, уникаючи ситуації, коли кожен учасник повторно відповідає на запит читання. UEFI, який використовується AMD, додає стан «Власника», що дозволяє кешу постачати дані іншим безпосередньо з модифікованого рядка, не записуючи спочатку дані назад у пам’ять, обмінюючись трохи більшою складністю протоколу на зменшений трафік шини пам’яті. Зі зростанням кількості ядер до десятків чистий моніторинг шини став вузьким місцем масштабування, оскільки кожне ядро повинно спостерігати за кожною транзакцією, що спонукало до переходу до каталог-орієнтованої коректності, де централізована або розподілена структура каталогу відстежує, які кеші містять певні рядки, щоб повідомлення про виключення надсилалися лише фактичним учасникам, а не всім. Незважаючи на ці варіації, фундаментальна логіка машини станів, яку ви бачите в цій симуляції – лінії переміщуються між приблизно аналогічними станами «винятковий», «спільний», «модифікований» та «недійсний» у відповідь на спостереження за шиною – залишається концептуальним фундаментом коректності в практично кожному багатоядерному чіпі, що постачається сьогодні, від мікроконтролерів для смартфонів до серверних процесорів з понад сотнею ядер.
Часті запитання
Що таке MESI?
MESI — це абревіатура, що позначає чотири стани, в яких може перебувати лінія кешу: Modified, Exclusive, Shared, та Invalid. Кожне з букв представляє собою окрему комбінацію валідності, виключної власності та того, чи відрізняється копія в кеші від основної пам'яті.
Чому існує окремий стан Exclusive від Shared, якщо обидва вони не змінені?
Exclusive повідомляє ядро, що воно єдине ядро, яке має дійсну копію, тому воно може безпосередньо перейти до стану Modified при наступному записі без будь-якого трафіку по шині. Shared означає, що інші кеші також можуть мати цю лінію, тому запис повинен спочатку розповсюдити інвалідацію для всіх інших учасників, що є дорожчим.
Що таке хибне спільне використання (false sharing) та чому це небезпечно?
Хибне спільне використання відбувається, коли дві незалежні змінні, які використовуються різними ядрами, випадково знаходяться на одній лінії кешу, що викликає трафік узгодження навіть без реальної залежності даних. Це небезпечно, оскільки воно непомітно руйнує багатопотокову продуктивність без виробництва неправильних результатів, що ускладнює його виявлення без спеціалізованих інструментів профілювання.
Чи масштабується MESI до процесорів з десятками ядер?
MESI на основі спостереження шини (bus-snooping) має труднощі при високій кількості ядер, оскільки кожне ядро повинно спостерігати за кожною транзакцією узгодження на спільній шині, що стає вузьким місцем. Великі багатоядерні та багатосокетні системи зазвичай використовують директорійну систему узгодження (directory-based coherence), яка явно відстежує учасників, щоб інвалідації надсилалися лише тим кешам, які дійсно їх потребують.
Як MESI відрізняється від MOESI або MESIF?
MOESI, який використовується AMD, додає стан Owned (Власний), що дозволяє модифікованій лінії безпосередньо ділитися з кешем без попереднього запису в пам'ять. MESIF, який використовується Intel, додає стан Forward (Передача), щоб лише один із кількох учасників відповідав на новий запит читання, зменшуючи надмірні відповіді по шині; обидва є оптимізаціями, що додаються до основної чотиристанової логіки MESI.
Спробуйте наживо
Усе, що вище, працює прямо у вашому браузері — відкрийте MESI Cache Coherence Protocol Explorer і змінюйте параметри під час роботи. Нічого не встановлюється, нічого не завантажується на сервер, уся модель живе в одній вкладці.
▶ Відкрити симуляцію MESI Cache Coherence Protocol Explorer