Скачиваний:
98
Добавлен:
03.06.2014
Размер:
5.64 Mб
Скачать

Мультипроцессоры с памятью совместного использования

593

цессорных системах каждый процессор имеет доступ к любому устройству вводавывода. Если все процессоры имеют равный доступ ко всем модулям памяти и всем устройствам ввода-вывода и каждый процессор взаимозаменим с другими процессорами, то такая система называется SMP (Symmetric Multiprocessor — симметричный мультипроцессор). Ниже мы будем говорить именно о таком типе систем.

Семантика памяти

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

Семантику памяти можно рассматривать как контракт между программным обеспечением и аппаратным обеспечением памяти [3]. Если программное обеспечение соглашается следовать определенным правилам, то память соглашается выдавать определенные результаты. Основная проблема здесь — каковы должны быть правила. Эти правила называются моделями согласованности. Было предложено и разработано множество таких правил.

Предположим, что процессор 0 записывает значение 1 в какое-то слово памяти, а немного позже процессор 1 записывает значение 2 в то же самое слово. Процессор 2 считывает это слово и получает значение 1. Должен ли владелец компьютера обратиться после этого в мастерскую? Это зависит от того, что обещано в контракте.

Строгая согласованность

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

Согласованность по последовательности

Следующая модель называется согласованностью по последовательности [79].

Здесь при наличии нескольких запросов на чтение и запись аппаратное обеспечение определяет порядок всех запросов, но все процессоры наблюдают одну и ту же последовательность запросов.

5 9 4 Глава 8. Архитектуры компьютеров параллельного действия

Рассмотрим один пример. Предположим, что процессор 1 записываетзначение 100 в слово х, а через 1 не процессор 2 записывает значение 200 в слово х. А теперь предположим, что через 1 не после начала второй записи (процесс записи может быть еще не закончен) два других процессора, 3 и 4, считывают слово х по два раза (рис. 8.15). Возможный порядок шести событий представлен в листингах, относящихся к этому рисунку. В первом из них процессор 3 получает значения (20Q, 200), и процессор 4 получает значения (200, 200). Во втором они получают (100, 200) и (200,200) соответственно. В третьем они получают (100,100) и (200,100) соответственно. Все эти варианты допустимы, как и некоторые другие, которые мы здесь непоказали.

Процессор

 

Запись

 

\

2

значения 200

Запись

 

Считывание

 

значения 100'

слова 2х

 

1

X

дважды

3

 

Считывание слова 2х дважды

4

Рис. 8.15. Два процессора записывают, адругиедва процессора считывают одно и то же слово из общей памяти

В листингах 8.1—8.3 представлены возможные варианты двух записей и четырех чтений.

Листинг8.1. Крис. 8.15(а)

W100

W200

R3=200

R3=200

R4=200

R4=200

Листинг 8.2. К рис. 8.15 (б)

W100

R3=100

W200

R4=200

R3=200

R4=200

Л и с т и н г 8 . 3 . Крис. 8.15 (в)

W200

R4=200

W100

R3=100

R4=100

R3=100

Мультипроцессоры с памятью совместного использования

595

Согласованная по последовательности память никогда не позволит процессору 3 получить значение (100, 200), если процессор 4 получил (200, 100). Если бы это произошло, с точки зрения процессора 3 это бы означало, что запись значения 100 процессором 1 завершилась раньше записи значения 200, которую осуществляет процессор 2. Это вполне возможно. Но с точки зрения процессора 4 это также значит, что запись процессором 2 числа 200 завершилась до записи процессором 1 числа 100. Сам по себе такой результат тоже возможен, но он противоречит первому результату. Согласованность по последовательности гарантирует единый глобальный порядок записей, который виден всем процессорам. Если процессор 3 видит, что первым было записано значение 100, то процессор 4 должен видеть тот же порядок.

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

Процессорная согласованность

Процессорная согласованность [48] — более проигрышная модель, но зато ее легче реализовать на больших мультипроцессорах. Она имеет два свойства:

1.Все процессоры воспринимают записи любого процессора в том порядке, в котором они начинаются.

2.Все процессоры видят записи в любое слово памяти в том же порядке, в котором они происходят.

Эти два пункта очень важны. В первом пункте говорится, что если процессор 1 начинает запись значений 1А, 1Ви1Св какое-либо место в памяти именно в таком порядке, то все другие процессоры видят эти записи в том же порядке. Иными словами, никогда не произойдет такого, чтобы какой-либо процессор сначала увидел значение 1В, а затем значение 1А. Второй пункт нужен, чтобы каждое слово в памяти имело определенное недвусмысленное значение после того, как процессор совершил несколько записей в это слово, а затем остановился. Все должны воспринимать последнее значение.

Даже при таких ограничениях у разработчика есть много возможностей. Посмотрим, что произойдет, если процессор 2 начинает записи 2А, 2В и 2С одновременно с тремя записями процессора 1. Другие процессоры, которые заняты считыванием слов из памяти, увидят какую-либо последовательность из шести записей, например, 1А, 1В, 2А, 2В, 1С, 2С или 2А, 1А, 2В, 2С, 1В, 1С и т. п. При процессорной согласованности не гарантируется, что каждый процессор видит один и тот же порядок (в отличие от согласованности по последовательности). Вполне может быть так, что одни процессоры воспринимают первый порядок из указанных выше, другие — второй порядок, а третьи — иной третий порядок. Единственное, что гарантируется абсолютно точно, — ни один процессор не увидит последовательность, в которой сначала идет 1В, а затем 1А.

5 96 Глава 8. Архитектуры компьютеров параллельного действия

Слабая согласованность

В следующей модели, слабой согласованности, записи, произведенные одним процессором, воспринимаются по порядку [33]. Один процессор может увидеть 1А до 1В, а другой — 1А после 1В. Чтобы внести порядок в этот хаос, в памяти содержатся переменные синхронизации либо операция синхронизации. Когда выполняется синхронизация, все незаконченные записи завершаются и ни одна новая запись не может начаться, пока не будут завершены все старые записи и не будет произведена синхронизация. Синхронизация приводит память в стабильное состояние, когда не остается никаких незавершенных операций. Сами операции синхронизации согласованы по последовательности, то есть если они вызываются несколькими процессорами, выбирается какой-то определенный порядок, причем все процессоры воспринимают один и тот же порядок.

При слабой согласованности время разделяется на последовательные периоды, разграниченные моментами синхронизации (рис. 8.16). Никакого определенного порядка для 1А и 1В не гарантируется, и разные процессоры могут воспринимать эти две записи в разном порядке, то есть один процессор может сначала видеть 1А, а затем 1В, а другой процессор может сначала видеть 1В, а затем 1 А. Такая ситуация допустима. Однако все процессоры видят сначала 1В, а затем 1С, поскольку первая операция синхронизации требует, чтобы сначала завершились записи 1 А, 1В и 2А, и только после этого начались записи 1С, 2В, ЗА или ЗВ. Таким образом, с помощью операций синхронизации программное обеспечение может вносить порядок в последовательность событий, хотя это занимает некоторое время.

 

 

Запись

 

 

 

 

Процессор А

 

1D 1Е

1F

Процессор В

 

 

2D

Процессор С

 

 

ЗА

ЗВ

ЗС

 

Момент синхронизации

Время

Рис. 8.16. Слабо согласованная память использует операции синхронизации, которые делят время на последовательные периоды

Свободная согласованность

Слабая согласованность — не очень эффективный метод, поскольку он требует завершения всех операций памяти и задерживает выполнение новых операций до тех пор, пока старые не будут завершены. При свободной согласованности дела обстоят гораздо лучше, поскольку здесь используется нечто похожее на критические секции программы [46]. Идея состоит в следующем. Если процесс выходит за пределы критической области, это не значит, что все записи должны немедленно

Мультипроцессоры с памятью совместного использования

597

завершиться. Требуется только, чтобы все записи были завершены до того, как любой процесс снова войдет в эту критическую область.

В этой модели операция синхронизации разделяется на две разные операции. Чтобы считать или записать общую переменную, процессор (то есть его программное обеспечение) сначала должен выполнить операцию acquire над переменной синхронизации, чтобы получить монопольный доступ к общим разделяемым данным. Затем процессор может использовать эти данные по своему усмотрению (считывать или записывать их). Потом процессор выполняет операцию release над переменной синхронизации, чтобы показать, что он завершил работу. Операция release не требует завершения незаконченных записей, но сама она не может быть завершена, пока не закончатся все ранее начатые записи. Более того, новые операции памяти могут начинаться сразу же.

Когда начинается следующая операция acqui re, производится проверка, все ли предыдущие операции rel ease завершены. Если нет, то операция acqui re задерживается до тех пор, пока они все не будут сделаны (а все записи должны быть завершены перед тем, как завершатся все операции release). Таким образом, если следующая операция acquire появляется через достаточно длительный промежуток времени после последней операции rel ease, ей не нужно ждать, и она может войти в критическую область без задержки. Если операция acquire появляется через небольшой промежуток времени после операции release, эта операция acquire (и все команды, которые следуют за ней) будет задержана до завершения всех операций release. Это гарантирует, что все переменные в критической области будут обновлены. Такая схема немного сложнее, чем слабая согласованность, но она имеет существенное преимущество: здесь не нужно задерживать выполнение команд так часто, как в слабой согласованности.

Архитектуры UMA SMP с шинной организацией

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

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

Чтобы разрешить эту проблему, нужно добавить кэш-память к каждому процессору, как показано на рис. 8.17, б. Кэш-память может находиться внутри микросхемы процессора, рядом с микросхемой процессора, на плате процессора. Допустимы комбинации этих вариантов. Поскольку теперь считывать слова можно из кэш-памяти, движения в шине будет меньше, и система сможет поддерживать большее количество процессоров.

5 9 8 Глава 8. Архитектуры компьютеров параллельного действия

Совместно используемая память

Процессор Процессор Память

Процессор Процессор Память

Кэш-память

Шина

Собственнаяпамять-

Совместно И(:пользуемая п<

\

Процессор Процессор Память

Кэш-память

Рис. 8.17.Три мультипроцессора на одной шине: без кэш-памяти (а); с кэш-памятью (б); с кэш-памятью и отдельными блоками памяти (в)

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

Отслеживаниеизмененийданныхвкэш-памяти

Предположим, что память согласована по последовательности. Что произойдет, если процессор 1 содержит в своей кэш-памяти строку, а процессор 2 пытается считать слово из той же строки кэш-памяти? При отсутствии специальных правил процессор 2 получит копию этой строки в свою кэш-память. В принципе помещение одной и той же строки в кэш-память дважды вполне приемлемо. А теперь предположим, что процессор 1 изменяет строку, и сразу после этого процессор 2 считывает копию этой строки из своей кэш-памяти. Он получит устаревшие данные, нарушая контракт между программным обеспечением и памятью. Ни к чему хорошему это не приведет.

Эта проблема, которую называют непротиворечивостью кэшей, очень важна. Если ее не разрешить, нельзя будет использовать кэш-память, и число мультипроцессоров, подсоединенных к одной шине, придется сократить до двух-трех. Специалистами было предложено множество различных решений (например, [47,109]). Хотя все эти алгоритмы, называемые протоколами когерентности кэширования,

Мультипроцессоры с памятью совместного использования

599

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

Во всех решениях контроллер кэш-памяти разрабатывается так, чтобы кэш-па- мять могла перехватывать запросы на шине, контролируя все запросы шины от других процессоров и других блоков кэш-памяти и предпринимая те или иные действия в определенных случаях. Эти устройства называются кэш-памятью с отслеживанием(snoopingcaches или snoopy caches), посколькуони отслеживают шину. Набор правил, которые выполняются кэш-памятью, процессорами и основной памятью, чтобы предотвратить появление различных вариантов данных в нескольких блоках кэш-памяти, формируют протокол когерентности кэширования. Единица передачи и хранения кэш-памяти называется строкой кэш-памяти. Обычно строка кэш-памяти равна 32 или 64 байтам.

Самый простой протокол когерентности кэширования называется сквозным кэшированием. Чтобы лучше понять его, рассмотрим 4 случая, приведенные в табл. 8.6. Если процессор пытается считать слово, которого нет в кэш-памяти, контроллер кэш-памяти загружает в кэш-память строку, содержащую это слово. Строку предоставляет основная память, которая в это: > протоколе всегда обновлена. В дальнейшем информация может считываться из кэш-памяти.

Таблица8.6. Сквозное кэширование. Пустые графы означают, что никакого действия не происходит

Действие

Локальный запрос

Удаленный запрос

Промахпри чтении

Вызов данных из памяти

 

Попадание при чтении

Использование данных

 

 

из локальной кэш-памяти

 

Промах при записи

Обновление данных в памяти

 

Попадание при записи

Обновление кэш-памяти

Объявление элемента кэш-

 

и основной памяти

памяти недействительным

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

Рассмотрим все эти действия снова, но теперь с точки зрения кэш-памяти с отслеживанием (крайняя правая колонка в табл. 8.6). Назовем кэш-память, которая выполняет действия, кэш-1, а кэш с отслеживанием — кэш-2. Если при считывании произошел промах кэша-1, он запрашивает шину, чтобы получить нужную строку из основной памяти. Кэш-2 видит это, но ничего не делает. Если нужная строка уже содержится в кэш-1, запроса шины не происходит, поэтому кэш-2 не знает о результативных считываниях из кэша-1.

Процесс записи более интересен. Если процессор 1 записывает слово, кэш-1 запрашивает шину как в случае промаха кэша, так и в случае попадания. Всегда призаписи кэш-2 проверяетналичиеусебя записываемого слова. Еслиданное слово

6 0 0 Глава 8. Архитектуры компьютеров параллельного действия

отсутствует, кэш-2 рассматривает это как промах отдаленной памяти и ничего не делает. (Отметим, что в табл. 8.6 промах отдаленной памяти означает, что слово не присутствует в кэш-памяти отслеживателя; не имеет значения, было ли это слово

вкэш-памяти инициатора или нет. Таким образом, один и тот же запрос может быть результативным логически и промахом для отслеживателя и наоборот.)

Атеперь предположим, что кэш-1 записывает слово, которое присутствует

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

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

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

Другой вариант — загрузка отслеживающей кэш-памяти при промахах. Такая загрузка никак не влияет на правильность выполнения алгоритма. Она влияет только на производительность. Возникает вопрос: какова вероятность, что только что записанное слово вскоре будет записано снова? Если вероятность высока, то можно говорить в пользу загрузки кэш-памяти при промахах записи (политика заполнения по записи). Если вероятность мала, лучше не обновлять кэш-память в случае промаха при записи. Если данное слово скоро будет считываться, оно все равно будет загружено после промаха при считывании, и нет смысла загружать его в случае промаха при записи.

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

Мультипроцессоры с памятью совместного использования

601

можно произвести много записей. Такой тип протокола называется протоколом

с обратной записью.

Протокол MESI

Один из популярных протоколов с обратной записью называется MESI (по первым буквам названий четырех состояний, М, Е, S и I) [109]. В его основе лежит протокол однократной записи [47]. Протокол MESI используется в Pentium II и других процессорах для отслеживания шины. Каждый элемент кэш-памяти может находиться в одном из следующих четырех состояний:

1.Invalid — элемент кэш-памяти содержит недействительные данные.

2.Shared — несколько кэшей могут содержать данную строку; основная память обновлена.

3.Exclusive — никакой другой кэш не содержит эту строку; основная память обновлена.

4.Modified — элемент действителен; основная память недействительна; копий элемента не существует.

При загрузке процессора все элементы кэш-памяти помечаются как недействительные. При первом считывании из основной памяти нужная строка вызывается в кэш-память данного процессора и помечается как Е (Exclusive), поскольку это единственная копия в кэш-памяти (рис. 8.18, а). При последующих считываниях процессор использует эту строку и не использует шину. Другой процессор может вызвать ту же строку и поместить ее в кэш-память, но при отслеживании исходный держатель строки (процессор 1) узнает, что он уже не единственный, и объявляет, что у него есть копия. Обе копии помечаются состоянием S (Shared) (см. рис. 8.18, б). При последующих чтениях кэшированных строк в состоянии S процессор не использует шину и не меняет состояние элемента.

Посмотрим, что произойдет, если процессор 2 произведет запись в строку кэшпамяти, находящуюся в состоянии S. Тогда процессор помещает сигнал о недействительности на шину, который сообщает всем другим процессорам, что нужно отбросить свои копии. Соответствующая строка переходит в состояние М (Modified) (см. рис. 8.18, в). Эта строка не записывается в основную память. Отметим, что если записываемая строка находится в состоянии Е, никакого сигнала о недействительности на шину передавать не следует, поскольку известно, что других копий нет.

А теперь рассмотрим, что произойдет, если процессор 3 считывает эту строку. Процессор 2, который в данный момент содержит строку, знает, что копия в основной памяти недействительна, поэтому он передает на шину сигнал, чтобы процессор 3 подождал, пока он запишет строку обратно в память. Как только строка записана в основную память, процессор 3 вызывает из памяти копию этой строки, и в обоих кэшах строка помечается как S (см. рис. 8.18, г). Затем процессор 2 записывает эту строку снова, что делает недействительной копию в кэш-памяти процессора 3 (см. рис. 8.18, в).

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

6 02

Глава 8. Архитектуры компьютеров параллельного действия

ситуация, в которой процессор записывает что-либо в некэшированную строку. Если применяется политика write-allocate, строка будет загружаться в кэш-память и помечаться как М (рис. 8.18, е). Если политика write-allocate не применяется, запись будет производиться непосредственно в основную память, а строка в кэшпамяти сохранена не будет.

Процессор 1

Процессор 2

Процессор 3

I

A

|

I

 

I

I

I

 

 

Exclsive

 

 

 

/

 

 

 

 

 

 

 

Кэш-память

 

 

 

 

 

 

 

а

 

Процессор 1

Процессор 2

Процессор 3

I

 

I

I

A

|

I

I

 

 

Shared

 

 

Shared

 

 

Процессор 1

Процессор 2

Процессор 3

I

I

I

A

I

I

I

Modified

Процессор 1

Процессор 2

Процессор 3

I

 

 

I

A

|

I

A

|

 

 

 

 

 

Shared

 

 

Shared

Процессор 1

Процессор 2

Процессор 3

I

 

I

J

А

I

I

 

I

 

 

 

 

 

Modified

 

 

 

Процессор 1

Процессор 2

Процессор 3

I

A

|

I

 

I

I

 

I

Modified

е

Память

Шина

Память

Шина

Память

Шина

Память

Шина

Память

Шина

Память

Шина

Процессор 1 считывает блок А

Процессор 2 считывает блок А

Процессор 2 записывает блок А

Процессор 3 считывает блок А

Процессор 2 записывает блок А

Процессор 1 записывает блок А

Рис. 8.18. Протокол MESI