Какой смысл MESI на Intel 64 и IA-32
- Смысл MESI заключается в том, чтобы сохранить понятие системы с общей памятью.
- Однако с буферами магазина все сложно:
- Память является последовательной после того, как данные попадают в кеши, реализующие MESI.
- Однако до этого каждое ядро может не соглашаться с тем, что находится в ячейке памяти X, в зависимости от того, что находится в буфере локального хранилища каждого ядра.
- Таким образом, кажется, что с точки зрения каждого ядра состояние памяти различно - оно не связно.
- Итак, почему мы беспокоимся о "частичном" соблюдении согласованности с МЭСИ?
Редактировать: существенное редактирование было сделано после некоторого дальнейшего сужения того, что действительно смущало меня. Я пытался сохранить общее представление об этом вопросе, чтобы сохранить актуальность полученных великих ответов.
2 ответа
Смысл MESI для x86 такой же, как и для любой системы с несколькими ядрами и процессорами: обеспечить согласованность кэша. Не существует "частичной когерентности", используемой для части когерентности кэша уравнения на x86: кэши полностью когерентны. Таким образом, возможные переупорядочения являются результатом как когерентной системы кэширования, так и взаимодействия с локальными компонентами ядра, такими как подсистема загрузки / хранения (особенно буферы хранения), и другими механизмами вне порядка.
Результатом этого взаимодействия является спроектированная модель сильной памяти, которую предоставляет x86, с ограниченным переупорядочением. Без связных кэшей вы не могли бы разумно реализовать эту модель вообще, или почти любую модель, которая была бы чем-то иным, кроме полностью слабой 1.
Ваш вопрос, похоже, включает в себя предположение о том, что существуют только возможные состояния "связное" и "все остальное". Кроме того, существует некоторая смесь идей когерентности кэша (которая в основном касается конкретно кэшей и в основном скрытой детали) и модели согласованности памяти, которая архитектурно определена и будет реализована каждой архитектурой 2. Википедия объясняет, что одно из различий между когерентностью кэша и согласованностью памяти заключается в том, что правила для первого применяются только к одному местоположению за раз, тогда как правила согласованности применяются ко всем местоположениям. На практике более важное различие заключается в том, что модель согласованности памяти является единственной архитектурно документированной.
Вкратце, Intel (и AMD аналогично) определяют конкретную модель согласованности памяти, x86-TSO 3, которая является относительно сильной в отношении моделей памяти, но все же слабее последовательной согласованности. Основные виды поведения, ослабленные по сравнению с последовательной последовательностью:
- Это более поздние грузы могут передать более ранние магазины.
- Эти магазины можно увидеть в другом порядке по сравнению с общим заказом магазина, но только по ядрам, которые выполняли один из магазинов.
Чтобы реализовать эту модель памяти, различные части должны играть по правилам для ее достижения. На всех последних версиях x86 это означает упорядоченные буферы загрузки и хранения, которые позволяют избежать запрещенных переупорядочений. Использование буфера хранилища приводит к двум переупорядочениям, упомянутым выше: без разрешения реализация будет очень ограниченной и, вероятно, намного медленнее. На практике это также означает полностью согласованные кэши данных, поскольку многие из этих гарантий (например, отсутствие переупорядочения нагрузки-нагрузки) было бы очень трудно реализовать без этого.
Чтобы обернуть все это:
- Согласованность памяти отличается от согласованности в кэше: первое - это то, что задокументировано и является частью модели программирования.
- На практике реализации x86 имеют полностью согласованные кеши, которые помогают им реализовать свою модель памяти x86-TSO, которая является достаточно сильной, но слабее последовательной согласованности.
- Наконец, возможно, ответ, который вы искали, другими словами: модель памяти, более слабая, чем последовательная согласованность, все еще очень полезна, так как вы можете программировать против нее, и в случае, если вам нужна последовательная согласованность для некоторых конкретных операций, вы вставляете правильные барьеры памяти 4.
- Если вы программируете в соответствии с моделью памяти, предоставляемой языком, такой как Java или C++11, вам не нужно беспокоиться об аппаратных особенностях, а не о модели языковой памяти, и компилятор вставляет барьеры, необходимые для соответствия модели языковой памяти семантика к аппаратному. Чем сильнее аппаратная модель, тем меньше требуется барьеров.
1 Если ваша модель памяти была абсолютно слабой, то есть не накладывала никаких ограничений на переупорядочение между ядрами, я полагаю, вы могли бы реализовать ее непосредственно в согласованной системе, не связанной с кэшем, дешевым способом для обычных операций, но тогда барьеры памяти потенциально могут стать очень дорого, так как им потребуется очистить потенциально большую часть локального частного кэша.
2 Различные микросхемы могут реализовываться внутренне по-разному, и, в частности, некоторые микросхемы могут реализовывать более сильную семантику, чем модель (т.е. некоторые разрешенные переупорядочения никогда не могут наблюдаться), но при отсутствии ошибок ни одна из них не будет реализовывать более слабую.
3 Это название дано в той статье, которую я использовал, потому что сама Intel не дает ей имя, и эта статья является более формальным определением, чем та, которую Intel дает менее формальной моделью в виде серии лакмусовых тестов.
На практике на x86 вы обычно используете заблокированные инструкции (используя lock
префикс), а не отдельные барьеры, хотя существуют и отдельные барьеры. Здесь я просто использую термин barries для обозначения как отдельных барьеров, так и семантики барьеров, встроенных в заблокированные инструкции.
Re: ваше редактирование, которое кажется новым вопросом: правильно, пересылка магазина "нарушает" согласованность. Ядро может видеть свои собственные магазины раньше, чем любое другое ядро может их видеть. Буфер хранилища не является связным.
Правила упорядочения памяти x86 требуют, чтобы загрузки стали глобально видимыми в программном порядке, но позволяют ядру загружать данные из своих собственных хранилищ, прежде чем они станут глобально видимыми. Ему не нужно притворяться, что он ждал, и проверять наличие неправильных предположений в порядке следования памяти, как это происходит в других случаях, когда загрузка выполняется раньше, чем говорит модель памяти.
Также связано; Может ли x86 переупорядочить узкий магазин с более широкой загрузкой, которая полностью его содержит? является конкретным примером того, как буфер хранилища + пересылка хранилища нарушают обычные правила упорядочения памяти. Посмотрите эту коллекцию сообщений списка рассылки Линуса Торвальдса, объясняющую влияние пересылки магазина на упорядочение памяти (и как это означает, что предложенная схема блокировки не работает).
Без какой-либо согласованности, как бы вы атомарно увеличивали общий счетчик или реализовывали другие атомарные операции чтения-изменения-записи, которые необходимы для реализации блокировок или для использования непосредственно в коде без блокировки. (См. Может ли num++ быть атомарным для int num?).
lock add [shared_counter], 1
в нескольких потоках в то же время никогда не теряет счет на фактическом x86, потому что lock
Префикс заставляет ядро сохранять исключительное владение строкой кэша от загрузки до тех пор, пока хранилище не перейдет на L1d (и, таким образом, не станет видимым глобально).
Система без когерентных кэшей будет позволять каждому потоку увеличивать свою собственную копию общего счетчика, и тогда окончательное значение в памяти будет получено из того потока, который последний раз очищал эту строку.
Разрешение различным кэшам хранить конфликтующие данные для одной и той же строки в долгосрочной перспективе, даже когда происходили другие загрузки / сохранения, и через барьеры памяти, допускало бы всевозможные странности.
Это также нарушило бы предположение, что чистое хранилище становится видимым для других ядер быстро. Если у вас вообще не было согласованности, то ядра могли бы продолжать использовать свою кэшированную копию общей переменной. Так что, если вы хотите, чтобы читатели заметили обновления, вы должны clflush
перед каждым чтением разделяемой переменной, что делает общий случай дорогим (когда никто не изменял данные с момента последней проверки).
MESI похож на систему push-уведомлений, вместо того, чтобы заставлять каждого читателя повторно проверять свой кеш при каждом чтении.
MESI (или согласованность в целом) позволяет таким вещам, как RCU (Read-Copy-Update), иметь нулевые накладные расходы для считывателей (по сравнению с однопоточными) в случае, когда структура общих данных не была изменена. См. https://lwn.net/Articles/262464/ и https://en.wikipedia.org/wiki/Read-copy-update. Основная идея заключается в том, что вместо блокировки структуры данных автор копирует все, модифицирует копию, а затем обновляет общий указатель, чтобы указать на новую версию. Таким образом, читатели всегда полностью свободны от ожидания; они просто разыменовывают (атомарный) указатель, и данные остаются горячими в своих кешах L1d.
Аппаратная согласованность чрезвычайно важна, и ее использует почти каждая архитектура SMP с разделяемой памятью. Даже ISA с гораздо более слабыми правилами упорядочения памяти, чем x86, например PowerPC, используют MESI.