- •8. Система памяти
- •8.1. Базовые концепции
- •8.2. Быстродействие, объем и стоимость
- •8.3.1. Функция отображения
- •8.3.2. Алгоритмы замещения
- •8.3.3. Примеры технологий отображения
- •8.3.4. Организация Кэша в коммерческих процессорах
- •8.4. Производительность
- •8.4.1. Чередование операций
- •8.4.2. Частота попаданий и накладные расходы при промахах
- •8.4.3. Кэши на микросхеме процессора
- •8.4.4. Другие способы увеличения быстродействия
- •8.5. Виртуальная память
- •8.5.1. Преобразование адресов
- •8.6. Требования к управлению памятью
8.3.2. Алгоритмы замещения
В кэше с прямым отображением позиция каждого блока определена раз и навсегда, поэтому никакая особая стратегия замены блоков ему не требуется. А вот в ассоциативном или множественно-ассоциативном кэше замена блоков может выполняться по-разному. Когда в кэш нужно будет поместить новый блок, но свободной позиции для него там не окажется, контроллер кэша должен выбрать один из старых блоков для перезаписи. От того, как он будет решать эту задачу, зависит производительность системы.
Главная идея, которой следует руководствоваться, принимая такое решение, состоит в следующем: в памяти должны оставаться блоки, для которых вероятность того, что они понадобятся в ближайшем будущем, максимальна. Но как их определить? Здесь можно опереться на принцип локализации ссылок. Так как повторяющиеся команды, лежащие в пределах некоторой области, выполняются в течение определенного времени, существует большая вероятность того, что блоки, обращение к которым производилось недавно, очень скоро потребуются снова. Поэтому, когда требуется освободить место для нового блока, имеет смысл удалить из кэша тот блок, к которому дольше всего не было обращений. Алгоритм работы этого блока называется алгоритмом удаления наиболее давно использовавшихся элементов (Least Recently Used, LRU).
Для использования алгоритма LRU контроллер кэша должен отслеживать обращения ко всем блокам кэша. Предположим, что ему нужно следить за обращениями к блоку LRU из четырехблочного множества множественно-ассоциативного кэша. Для каждого блока может использоваться 2-разрядный счетчик. При попадании в кэш счетчик соответствующего блока устанавливается в 0. Счетчики, значения которых были больше значения данного счетчика, увеличиваются на 1. Когда в кэше не оказывается нужного блока, а в множестве еще есть место, счетчик нового блока устанавливается в 0, а значения других счетчиков увеличиваются на 1. Если же множество заполнено, блок, счетчик которого равен 3, удаляется, а на его место помещается новый блок. Значения остальных трех счетчиков увеличиваются на 1. Нетрудно убедиться, что при использовании такого алгоритма значения счетчиков четырех блоков всегда будут разными.
Алгоритм LRU очень популярен. В большинстве случаев он работает прекрасно, но иногда его применение может привести к снижению производительности, например, при обращении к последовательным элементам массива, который слишком велик и не помещается в кэше целиком. Для того чтобы повысить производительность алгоритма, можно внести в него некоторую долю случайного выбора.
На практике используются и некоторые другие алгоритмы замещения. Правило замены самого «старого» блока кажется наиболее логичным, но оно не принимает в расчет частоту обращений к хранящимся в кэше блокам. Поэтому оно не так эффективно, как алгоритм LRU. Самым простым решением является случайный выбор перезаписываемого блока, и, что интересно, практика показывает его эффективность.
8.3.3. Примеры технологий отображения
Ниже будет рассмотрен пример, демонстрирующий различия между разными технологиями отображения памяти на кэш. Предположим, что у процессора имеются отдельные кэши команд и данных. Для упрощения примера будем считать, что в кэше данных помещается только восемь блоков. Блок состоит из одного 16-разрядного слова, а память адресуется пословно посредством 16-разрядных адресов. (Это не реалистичные параметры, но они удобны для нашего примера.) Для замены блоков в кэше используется алгоритм LRU.
Давайте проанализируем изменения в кэше данных, вызванные выполнением следующей задачи. Массив чисел А размером 4 х 10, в котором каждое число занимает одно слово, хранится в основной памяти по шестнадцатеричным адресам от 7А00 до 7А27. Элементы этого массива хранятся в порядке следования столбцов. На рис. 8.7 показано, как выделяются теги из адресов памяти при разных технологиях отображения. Обратите внимание, что в нашем примере нет специальных битов, используемых для идентификации слова внутри блока, как на рис. 8.4, 8.5, 8.6, поскольку мы предполагаем, что каждый блок содержит только одно слово. Приложение нормализует значения элементов первой строки массива А относительно среднего значения элементов этой строки. Таким образом, нам нужно вычислить среднее значение элементов строки и разделить на него значение каждого из элементов. Эту задачу можно выразить так:
Код, выполняющий указанную задачу, приведен на рис. 8.8. В программе на машинном языке для ссылки на элементы массива будут использоваться адреса памяти. Для хранения суммы и среднего значения предназначены переменные SUM и AVE. Эти переменные, равно как и индексные переменные i и j, при вычислениях хранятся в регистрах процессора.
Рис. 8.7. Массив, хранящийся в основной памяти
Рис. 8.8. Код для примера
Кэш с прямым отображением
На рис. 8.9 показано, как изменяется содержимое кэша с прямым отображением. В столбцах таблицы приведено содержимое кэша после проходов по двум циклам программы, представленной на рис. 8.8. Например, после второго прохода по первому циклу (j = 1) в кэше содержатся элементы А(0,0) и А(0,1). Они хранятся в блоках 0 и 4 с учетом значений трех младших разрядов их адресов. На следующем проходе элемент А(0,0) заменяется элементом А(0,2), имеющим тот же адрес блока. Обратите внимание, что элементы массива соответствуют только двум блокам кэша, а остальные блоки остаются неизменными — в конце процесса нормализации они содержат те же данные, что до его начала.
Позиция блока
0 1 2 3 4 5 6 7
|
Содержимое кэша данных после прохода по циклу:
| ||||||||
j=1
|
j=3
|
j=5
|
j=7
|
j=9
|
i=6
|
i=4
|
i=2
|
i=0
| |
А(0,0)
|
А(0,2)
|
А(0,4)
|
А(0,б)
|
А(0,8)
|
А(0,6)
|
А(0,4)
|
А(0,2)
|
А(0,0)
| |
|
|
|
|
|
|
|
|
| |
|
|
|
|
|
|
|
|
| |
|
|
|
|
|
|
|
|
| |
А(0,1)
|
А(0,3)
|
А(0,5)
|
А(0,7)
|
А(0,9)
|
А(0,7)
|
А(0,5)
|
А(0,3)
|
А(0,1)
| |
|
|
|
|
|
|
|
|
| |
|
|
|
|
|
|
|
|
| |
|
|
|
|
|
|
|
|
|
Рис. 8.9. Содержимое кэша данных с прямым отображением
После десяти проходов по первому циклу (j = 9) в кэше хранятся элементы А(0,8) и А(0,9). Поскольку при выполнении второго цикла элементы обрабатываются в обратном порядке, то элементы, необходимые на первых двух проходах этого цикла, будут находиться в кэше. На третьем проходе (i = 7) элемент А(0,8) будет заменен элементом А(0,7), затем элементом А(0,6) и т. д. Таким образом, во время выполнения второго цикла в кэше будет заменено восемь элементов.
Нужно иметь в виду, что каждому блоку в кэше соответствует определенный тег. На рисунке мы их не показываем, чтобы не занимать лишнего места.
Ассоциативный кэш
Как изменяется содержимое полностью ассоциативного кэша, можно судить по рис. 8.10. Если перед началом выполнения программы кэш пуст, на первых восьми проходах по циклу элементы массива копируются в последовательные позиции. Для эффективного кэширования данных важно, чтобы второй цикл перебирал элементы массива в обратном порядке. Интересно посмотреть, что получится, если второй цикл пройдет по элементам в том же порядке, что и первый. Если используется алгоритм LRU, то во втором цикле все элементы будут перезаписаны еще до того, как они будут обработаны. Этого не произойдет, если применить алгоритм замены со случайной выборкой блоков.
Позиция блока
0 1 2 3 4 5 6 7
|
Содержимое кэша данных после выполнения цикла:
| ||||
j = 7
|
j = 8
|
j = 9
|
i = 1
|
i = 0
| |
А(0,0)
|
А(0,8)
|
А(0,8)
|
А(0,8)
|
А(0,0)
| |
А(0,1)
|
А(0,1)
|
А(0,9)
|
А(0,1)
|
А(0,1)
| |
А(0,2)
|
А(0,2)
|
А(0,2)
|
А(0,2)
|
А(0,2)
| |
А(0,3)
|
А(0,3)
|
А(0,3)
|
А(0,3)
|
А(0,3)
| |
А(0,4)
|
А(0,4)
|
А(0,4)
|
А(0,4)
|
А(0,4)
| |
А(0,5)
|
А(0,5)
|
А(0,5)
|
А(0,5)
|
А(0,5)
| |
А(0,6)
|
А(0,6)
|
А(0,6)
|
А(0,6)
|
А(0,6)
| |
А(0,7)
|
А(0,7)
|
А(0,7)
|
А(0,7)
|
А(0,7)
|
Рис. 8.10. Содержимое ассоциативного кэша данных
Множественно-ассоциативный кэш
Предположим, что множественно-ассоциативный кэш данных разделен на два множества по четыре блока. Это означает, что младший бит адреса будет определять множество, которому соответствует данный блок памяти. Старшие 15 разрядов составляют тег.
Процесс изменения содержимого кэша показан на рис. 8.11. Поскольку все блоки данных нашей программы в памяти имеют четные адреса, все они отображаются на множество 0. Из-за этого шесть элементов при выполнении второго цикла перезагружаются.
Даже из этих простых примеров видно, что полностью ассоциативное отображение наиболее эффективно, за ним следует множественно-ассоциативное отображение, а наименее эффективно прямое отображение. Однако вследствие того, что реализация полностью ассоциативного отображения слишком дорого стоит, в качестве компромиссного решения, как правило, используется множественно-ассоциативное отображение.
Множество 1
Множество 2
|
Содержимое кэша данных после выполнения цикла:
| |||||
j = 3
|
j = 7
|
j = 9
|
i = 4
|
i = 2
|
i = 0
| |
А(0,0)
|
А(0,4)
|
А(0,8)
|
А(0,4)
|
А(0,4)
|
А(0,0)
| |
А(0,1)
|
А(0,5)
|
А(0,9)
|
А(0,5)
|
•А(0,5)
|
А(0,1)
| |
А(0,2)
|
А(0,6)
|
А(0,6)
|
А(0,6)
|
А(0,2)
|
А(0,2)
| |
А(0,3)
|
А(0,7)
|
А(0,7)
|
А(0,7)
|
А(0,3)
|
А(0,3)
| |
|
|
|
|
|
| |
|
|
|
|
|
| |
|
|
|
|
|
| |
|
|
|
|
|
|
Рис. 8.11. Содержимое множественно-ассоциативного кэша данных