Лекция № 7: Кэш процессора
Страницы: 1 2
Кэш процессора — это быстродействующая память небольшого объёма, используемая для уменьшения (в среднем) времени доступа процессора к медленной оперативной памяти. Кэш хранит копию части данных оперативной памяти. Уменьшение времени доступа происходит из-за того, что большинство данных, требуемых процессором, оказываются в кэше, и количество обращений к оперативной памяти снижается.
Кэш особенно актуален в современных системах, в которых велик разрыв между скоростью работы процессора и скоростью работы оперативной памяти (рисунок 1). Если данные, требуемые процессору для дальнейших вычислений, находятся в оперативной памяти (а не в кэше), то процессор будет вынужден их ожидать, пропуская десятки операций. Если же данные находятся в кэше, то они могут быть переданы процессору в ритме, необходимом для его безостановочной работы.

Рисунок 1. Соотношение скорости работы процессоров
и скорости работы оперативной памяти. Источник
Кэш состоит из собственно кэш-памяти, и кэш-контроллера (рисунок 2). Кэш-контроллер управляет кэш-памятью: загружает в неё нужные данные из оперативной памяти, и возвращает, когда нужно, модифицированные процессором данные в оперативную память.

Рисунок 2. Идея функционирования кэша
Важно понимать, что кэш всегда «полон», так как оставлять часть кэш-памяти «пустой» было бы совершенно нерационально. Новые данные попадают в кэш только путём вытеснения (замещения) каких-либо старых данных.
Обычно кэш прозрачен для прикладных программ. Это означает, что программы работают с памятью, не заботясь о существовании кэша: кэш «перехватыват» запросы к оперативной памяти, предоставляя программе требуемые данные. Однако, современные системы позволяют приложению «подсказать» кэшу правильное поведение. Это может быть сделано, например, при помощи команд предварительной загрузки данных в кэш и записи данных в память, минуя кэш. Бывают также вычислительные системы, в которых кэш полностью управляется программой: программа может независимо работать с кэш-памятью и оперативной памятью, и сама решает, какие именно данные хранить в кэше.
Когда процессор хочет прочесть (или записать) данные по какому-либо адресу оперативной памяти, он передаёт этот адрес в контроллер кэш-памяти. Контроллер по некоторому алгоритму (смотрите далее) определяет, содержатся ли в кэш-памяти данные, соответствующие полученному от процессора адресу. Если данные найдены (это событие называется попаданием в кэш, cache hit), то кэш-контроллер выдаёт требуемые данные процессору (в случае чтения), либо перезаписывает их полученными от процессора данными (в случае записи). Если же данные не найдены (промах кэша, cache miss), то производится обращение к оперативной памяти, и процессор вынужден ждать.
Эффективностью кэша называется отношение числа попаданий кэша к общему количеству обращений процессора. Эффективность, таким образом, — это число от 0 до 1. Нулевая эффективность означает, что кэш нисколько не ускорил работу системы; эффективность, равная единице, означает, что ускорение максимально, и время обращения к памяти определяется скоростью работы кэша, а не скоростью работы оперативной памяти. Эффективность кэша зависит от следующих факторов:
- Объём кэша. Чем больше объём кэша, тем бо́льшую часть требуемых программе данных он может в себе содержать, тем реже будут происходить обращения к оперативной памяти, и тем выше будет общее быстродействие системы.
- Алгоритм функционирования кэша. К сожалению, зачастую объёма кэш-памяти недостаточно для того, чтобы вместить все необходимые для вычислений данные. В этом случае кэш-контроллер должен «решить», какие именно данные следует держать в кэше. Поэтому кроме объёма кэша важным является алгоритм его функционирования: кэш, оснащённый хорошим алгоритмом, будет гораздо эффективнее использовать свой объём, храня меньше ненужных данных.
- Выполняемая процессором программа. Кэш оказывается эффективным потому, что большинство компьютерных программ обращаются к памяти не случайным образом, а закономерно. Чем лучше кэш-контроллер может «предсказать» обращения приложения к памяти, тем выше эффективность.
Закономерность обращений программы к оперативной памяти обычно выражается в том, что обращения к памяти обладают временно́й и пространственной локальностью:
- Временна́я локальность: если произошло обращение к ячейке оперативной памяти, то с большой вероятностью эта ячейка памяти вскоре понадобится снова.
- Пространственная локальность: если произошло обращение к ячейке оперативной памяти, то с большой вероятностью будет произведено обращение к соседним ячейкам памяти.
Используя эти предположения, кэш-контроллер прогнозирует обращения процессора к памяти, заранее загружая в кэш-память необходимые данные. Рассмотрим некоторые архитектуры кэшей и алгоритмы, которые в них используются.
Полностью ассоциативный кэш
Пусть адрес байта оперативной памяти состоит из 32-х бит (4-х байт). Составим кэш из строк: пусть каждая строка содержит (хранит) адрес и байт, который соответствует этому адресу в оперативной памяти. Хранимый адрес принято называть тегом (tag), чтобы не путать его с адресом (номером) строки́ кэша.
Такой кэш называется полностью ассоциативным, так как любой байт оперативной памяти может оказаться в любой строке кэша. Пусть, кроме того, каждая строка кэша снабжена устройством, которое сравнивает тег, хранящийся в строке, с адресом памяти, к которому обращается процессор, и, в случае совпадения, выдаёт соответствующий байт данных (рисунок 3). Про такую память говорят, что она адресуется данными.

Рисунок 3. Пример поиска элемента в кэше по его адресу.
Данные приведены в шестнадцатеричном виде
Когда процессор хочет прочесть данные по какому-либо адресу оперативной памяти, он передаёт этот адрес в контроллер кэш-памяти. Кэш осуществляет одновременное сравнение всех имеющихся у него тегов с переданным адресом. Если адрес найден, то кэш выдаёт требуемый байт данных (рисунок 3). Если же данные не найдены (промах кэша), то производится обращение к оперативной памяти.
Скорее всего, только что прочитанные данные вскоре понадобятся вновь, поэтому, в случае промаха кэша, их нужно занести в кэш. Но кэш всегда полон. Это означает, что перед занесением новых данных какие-то другие данные из него нужно выбросить. Алгоритм, определяющий, какие данные нужно выбросить из кэша, называется политикой замещения данных.
Выбрасывать нужно тот элемент, к которому наиболее долго не будет обращений. Но, так как кэш не знает, какие обращения к памяти будут в будущем, он вынужден использовать какое-то правило, которое бы хорошо «угадывало» нужный элемент. Например, можно выбрасывать случайную строку из кэша. Но на практике чаще всего выбрасывается тот элемент, который дольше всех не использовался. Мотивация такая: «если строка долго не использовалась, то она, скорее всего, ещё не скоро понадобится вновь».
Но как определить, какая строка дольше всех не использовалась? Пусть, например, в кэше имеются 65536 строк. Тогда в каждую строку кэша можно добавить двухбайтовое целое число, которое будет обозначать «возраст» этой строки́. Строка, к которой обращались наиболее давно, имеет возраст, равный 65535. Строка, к которой было произведено обращение в последнюю очередь, имеет возраст 0.

Рисунок 4. Дополнительные байты для хранения возраста строки́
Пусть произошло попадание в кэш, и соответствующая строка имела возраст N. Присвоим ей возраст 0, а возрасты всех остальных строк, которые были меньше N, увеличим на единицу. Нетрудно видеть, что после такой операции все элементы кэша вновь имеют правильный возраст, соответствующий порядку их использования. Чтобы ускорить эту процедуру, нужно снабдить все стро́ки кэша устройствами, одновременно добавляющими единицу к возрасту своих строк. В случае промаха кэша из него выбрасывается элемент с максимальным (65535) возрастом, все возрасты увеличиваются на единицу, а новый элемент получает возраст 0.
Теперь рассмотрим процесс записи. Пусть процессор хочет записать данные в память. Скорее всего, эти данные вскоре понадобятся вновь, поэтому они должны быть записаны в кэш. Но нужно ли писать их при этом ещё и в оперативную память? Если данные в оперативную память записываются одновременно с записью в кэш, то он называется кэшом со сквозной записью.
Однако, если запись в каждую строку кэша происходит в среднем более одного раза до смены этой строки́, то выгодна другая стратегия. Очень частый сценарий работы с конкретным элементом памяти — «запись, чтение, запись, чтение, ...». Здравый смысл подсказывает, что запись в оперативную память нужно произвести лишь в самом конце подобной цепочки. А когда у цепочки операций со строкой кэша наступает конец? Тогда, когда эта строка выбрасывается из кэша.
Таким образом, когда строка выбрасывается из кэша, она должна быть записана в оперативную память. Но что, если она не была изменена? Тогда её не нужно записывать. Для того, чтобы отслеживать изменения строк кэша, снабдим каждую строку дополнительным битом (рисунок 5), который будет устанавливаться в единицу при модификации данных в строке. Тогда при вытеснении строки́ нужно будет проверить этот бит, и, если он равен единице, произвести запись в оперативную память.

Рисунок 5. Бит модификации строки́
Итак, процессор записывает данные в память. Если эти данные есть в кэше, то они перезаписываются и помечаются, как изменённые. Если данных в кэше нет, то самая старая строка вытесняется, и на её место записываются новые данные, опять же помеченные как изменённые. Недостаток такого подхода в том, что при промахе кэша теперь часто будут происходить две операции: запись данных старой строки́ из кэша в оперативную память, и загрузка требуемых данных из памяти в кэш. Поэтому иногда делают так, что кэш в свободное время сам переписывает модифицированные данные в оперативную память, обнуляя соответствующие биты модификации.
Нам осталось решить две проблемы:
- Наш кэш хорошо справляется с временно́й локальностью обращения к данным, но он совершенно не адаптирован к пространственной локальности. Как быть, если процессор обращается к одному байту оперативной памяти, затем к соседнему, и так далее?
- Разработанный кэш будет иметь очень высокие накладные расходы при аппаратной реализации. Действительно, каждая строка кэша, помимо единственного байта данных, содержит: четыре байта адреса, устройство сравнения адресов, два байта для хранения возраста строки́, устройство для наращивания возраста, и бит, обозначающий модификацию строки́.
Для решения этих проблем будем хранить в каждой строке кэша не один байт данных, а несколько байт, идущих подряд в оперативной памяти. Тегом строки́ будем считать адрес первого содержащегося в ней байта. Рассмотрим пример, когда в строке кэша хранятся 16 байт. Для того, чтобы данные в строках не могли «перекрываться» (перекрытие данных значительно усложнит логику работы), потребуем, чтобы теги всех строк были кратны 16. Тогда 4 последних бита тега будут равны нулю, и их не нужно хранить. Мы видим, что отношение «полезного» объёма кэша к общему значительно улучшилось (рисунок 6).

Рисунок 6. Каждая строка кэша имеет несколько байт данных
Чтобы скорость работы кэша не снизилась из-за того, что при каждом промахе требуется загружать из памяти сразу 16 байт, снабдим оперативную память «широкой» шиной данных, которая будет передавать в кэш все 16 байт одновременно.
При поиске данных в таком кэше по адресу следует взять старшие 28 бит адреса, и подать их на сравнение в стро́ки кэша. Если строка найдена, то нужно взять из строки́ байт, определяемый младшими четырьмя битами адреса. Если строка не найдена, то требуется обращение к оперативной памяти.
Теперь, если процессор будет запрашивать байты из памяти подряд, то лишь каждое 16-е обращение к памяти будет приводить к промаху кэша. Конечно, есть и недостаток: соседние 15 байт будут загружаться (и занимать часть кэш-памяти) даже тогда, когда они процессору не понадобятся. Можно усложнить алгоритм, чтобы чтение или запись данных подряд вообще не приводило к промахам кэша: для этого нужно загружать в кэш следующие 16 байт, если произошло обращение к последнему байту строки́.
Страницы: 1 2.
Четыре отзыва на запись «Лекция № 7: Кэш процессора»
Автор: Андрей. Дата: 20-го апреля 2010 г. Время: 12:16.
Спасибо большое за материал.
Автор: Антон. Дата: 26-го января 2011 г. Время: 20:11.
Спасибо автору, статья отличная!
Автор: Вася. Дата: 25-го ноября 2011 г. Время: 13:52.
>Если данных в кэше нет, то самая старая строка вытесняется, и на её место записываются новые данные, опять же помеченные как изменённые
Не понятно — если линия считана и уже помечена как изменённая — тогда её надо будет сбросить в память даже если процессор в неё ничего не писал.
Автор: Вася. Дата: 25-го ноября 2011 г. Время: 13:56.
А, в смысле, имелось в виду что не загружена из памяти, а что процессор в неё записал, спасибо, теперь понятно.