Boroda_2
.doc
А теперь вернемся к циклу 2. Мы можем разбить микрокоманду swap2 на микрошаги и начать их выполнение. В цикле 2 мы копируем значение SP в регистр В, затем пропускаем значение через АЛУ в цикле 3 и, наконец, сохраняем его в регистре MAR в цикле 4. Пока все хорошо. Должно быть ясно, что, если мы сможем начинать новую микрокоманду в каждом цикле, скорость работы машины увеличится в три раза. Такое повышение скорости происходит за счет того, что машина Mic-З производит в три раза больше циклов в секунду, чем Mic-2. Фактически мы построили конвейерный процессор.
К сожалению, мы наткнулись на преграду в цикле 3. Прекрасно было бы начать микрокоманду swap3, но эта микрокоманда сначала пропускает значение MDR через АЛУ, а значение MDR не может быть получено из памяти до начала цикла 5. Ситуация, когда следующий микрошаг не может начаться, потому что перед этим нужно получить результат выполнения предыдущего микрошага, называется реальной взаимозависимостью, или RAW-взаимозависимостью (Read After Write — чтение после записи). Взаимозависимости иногда называют рисками.
В такой ситуации требуется считать значение регистра, которое еще не записано. Единственное разумное решение в данном случае — отложить начало микрокоманды swap3 до того момента, когда значение MDR станет доступным, то есть до пятого цикла. Ожидание нужного значения называется простоем. После этого мы можем начинать выполнение микрокоманд в каждом цикле, поскольку таких ситуаций больше не возникает, хотя имеется пограничная ситуация: микрокоманда swap6 считывает значение регистра Н в цикле, который следует сразу после записи этого регистра в микрокоманде swap3. Если бы значение этого регистра считывалось в микрокоманде swap5, машине пришлось бы простаивать один цикл.
Хотя программа Mic-З требует больше циклов, чем программа Mic-2, она работает гораздо быстрее. Если время цикла микроархитектуры Mic-З составляет АТ не, то для выполнения команды SWAP машине Mic-З требуется 11 АТ не, а машине Mic-2 — 6 циклов по ЗДТ не каждый, то есть всего 18АТ не. Конвейеризация увеличивает быстродействие компьютера даже несмотря на то, что один раз приходится простаивать из-за явления реальной взаимозависимости.
Конвейеризация является ключевой технологией во всех современных процессорах, поэтому важно хорошо в ней разбираться. На рис. 4.22 графически проиллюстрирована конвейеризация тракта данных, изображенного на рис. 4.21. В первой колонке показано, что происходит во время цикла 1, вторая колонка представляет цикл 2 и т. д. (предполагается, что простоев нет). Закрашенная область на рисунке для цикла 1 и команды 1 означает, что блок выборки команд занят вызовом команды 1. В цикле 2 значения регистров, вызванных командой 1, загружаются в А и В, а в это время блок выборки команд занимается вызовом команды 2, что также показано закрашенными серыми прямоугольниками.
Во время цикла 3 команда 1 использует АЛУ и схему сдвига, регистры А и В загружаются для команды 2, вызывается команда 3. Наконец, во время цикла 4 работают все 4 команды одновременно. Сохраняются результаты выполнения команды 1, АЛУ выполняет вычисления для команды 2, регистры А и В загружаются для команды 3, вызывается команда 4.
Если бы мы показали цикл 5 и следующие, модель была бы точно такой же, как в цикле 4: все четыре части тракта данных работали бы независимо друг от друга. Данный конвейер содержит 4 ступени: для вызова команд, для доступа к операндам, для работы АЛУ и для записи результата обратно в регистры. Он похож на конвейер, изображенный на рис. 2.3, а, только у него отсутствует ступень декодирования (расшифровки). Здесь важно подчеркнуть, что, хотя выполнение одной команды занимает 4 цикла, в каждом цикле начинается новая команда и завершается предыдущая.
Можно рассматривать схему на рис. 4.22 не вертикально (по колонкам), а горизонтально (по рядам). При выполнении команды 1 в цикле 1 функционирует блок выборки команд. В цикле 2 значения регистров помещаются на шины А и В. В цикле 3 работают АЛУ и схема сдвига. Наконец, в цикле 4 полученные результаты сохраняются в регистрах. Отметим, что имеется 4 доступных устройства и во время каждого цикла определенная команда использует только одно из них, оставляя свободными другие устройства для других команд.
Проведем аналогию с конвейером на заводе, производящем автомобили. Чтобы изложить суть работы такого конвейера, представим, что ровно каждую минуту звучит гонг, и в этот момент все автомобили передвигаются по конвейеру на один шаг. На каждом шаге рабочие выполняют определенную операцию с автомобилем, который оказывается перед ними, например ставят колеса или тормоза. При каждом ударе гонга (это — 1 цикл) очередная заготовка поступает на конвейер и один собранный автомобиль сходит с конвейера. Таким образом, завод выпускает один автомобиль в минуту независимо от того, сколько времени занимает сборка одного автомобиля. В этом и состоит суть конвейера. Такой подход в равной степени применим и к процессорам, и к автомобилям.
Семиступенчатый конвейер — микроархитектура Mic-4
Мы не упомянули о том факте, что каждая микрокоманда выбирает следующую за ней. Большинство из них просто выбирают очередную команду в текущей последовательности, но последняя из них, например swap6, часто совершает меж- уровневый переход, который останавливает работу конвейера, поскольку после этого перехода вызывать команды заранее уже бессмысленно. Поэтому нам нужно придумать лучшую технологию.
Следующая (и последняя) микроархитектура — Mic-4. Ее основные компоненты представлены на рис. 4.23, но большая их часть не показана, чтобы сделать схему более понятной. Как и Mic-З, эта микроархитектура содержит блок выборки команд (IFU), который заранее вызывает слова из памяти и сохраняет различные значения MBR.
Блок выборки команд передает входящий поток байтов в новый компонент — блок декодирования. Этот блок содержит внутреннее ПЗУ, которое индексируется кодом IJVM-операции. Каждый элемент (ряд) блока состоит из двух частей: поля длины IJVM-команды и индекса в другом ПЗУ — ПЗУ микроопераций. Длина IJVM-команды нужна для того, чтобы блок декодирования мог разделить входящий поток байтов и установить, какие байты являются кодами операций, а какие — операндами. Если длина текущей команды составляет 1 байт (например, длина команды POP), то блок декодирования определяет, что следующий байт — это код операции. Если длина текущей команды составляет 2 байта, блок декодирования определяет, что следующий байт — это операнд, сразу за которым следует другой код операции. Когда появляется префиксная команда WIDE, следующий байт преобразуется в специальный расширенный код операции, например, WIDE плюс IL0AD превращается в WIDE IL0AD.
Блок декодирования передает индекс в ПЗУ микроопераций, который он находит в своей таблице, следующему компоненту, блоку формирования очереди. Этот блок содержит логические схемы и две внутренние таблицы одну для ПЗУ и вторую для ОЗУ. В ПЗУ находится микропрограмма, причем каждая IJVM-команда состоит из микроопераций. Эти микрооперации должны располагаться в строгом порядке, и, например, переход из wide_iload2 в iload2, который допустим в микроархитектуре Mic-2, не разрешается. Каждая последовательность микроопераций должна выполняться полностью, в некоторых случаях последовательности дублируются.
Структура микрооперации сходна со структурой микрокоманды (см. рис. 4.4), только в данном случае поля NEXT_ADDRESS и JAM отсутствуют и требуется новое поле для определения входа на шину А. Имеется также два новых бита: бит завершения и бит перехода. Бит завершения устанавливается на последней микрооперации каждой последовательности (чтобы обозначить эту операцию). Бит перехода нужен для указания на микрооперации, которые являются условными микропереходами. По формату они отличаются от обычных микроопераций. Они состоят из битов JAM и индекса в ПЗУ микроопераций. Микрокоманды, которые раньше осуществляли какие-либо действия с трактом данных, а также выполняли условные микропереходы (например, iflt4), теперь нужно разбивать на две микрооперации.
Блок формирования очереди работает следующим образом. Он получает от блока декодирования индекс микрооперации в ПЗУ микроопераций. Затем он отыскивает микрооперацию и копирует ее во внутреннюю очередь. После этого он копирует очередную микрооперацию в ту же очередь, а также микрооперацию, следующую за этой микрооперацией. Так продолжается до тех пор, пока не появится микрооперация с битом завершения. Тогда блок копирует эту последнюю микрооперацию и останавливается. Если блоку не встретилась микрооперация с битом перехода и у него осталось достаточно свободного пространства, он посылает сигнал подтверждения приема блоку декодирования. Когда блок декодирования принимает сигнал подтверждения, он посылает блоку формирования очереди следующую IJVM-команду.
Таким образом, последовательность IJVM-команд в памяти в конечном итоге превращается в последовательность микроопераций в очереди. Эти микрооперации передаются в регистры MIR, которые посылают сигналы тракту данных. Но есть еще один фактор, который нам нужно учесть: поля каждой микрооперации не действуют одновременно. Поля А и В активны во время первого цикла, поле АЛУ активно во время второго цикла, поле С активно во время третьего цикла, а все операции с памятью происходят в четвертом цикле.
Чтобы все эти операции выполнялись правильно, мы ввели 4 независимых регистра MIR в схему на рис. 4.23. В начале каждого цикла (на рис. 4.2 это время Aw) значение MIR3 копируется в регистр MIR4, значение MIR2 — в регистр MIR3, значение MIR1 — в регистр MIR2, а в MIR1 загружается новая микрооперация из очереди. Затем каждый регистр MIR выдает сигналы управления, но используются только некоторые из них. Поля А и В из регистра MIR1 применяются для выбора регистров, которые запускают защелки А и В, а поле АЛУ в регистре MIR1 не используется и не связано ни с чем на тракте данных.
В следующем цикле микрооперация передается в регистр MIR2; выбранные регистры находятся в защелках А и В. Поле АЛУ теперь используется для запуска АЛУ. В следующем цикле поле С запишет результаты обратно в регистры. После этого микрооперация передается в регистр MIR4 и инициирует любую необходимую операцию памяти, используя загруженное значение регистра MAR (или MDR для записи).
Нужно обсудить еще один аспект микроархитектуры Mic-4 — микропереходы. Некоторым IJVM-командам нужен условный переход, который осуществляется с помощью бита N. Когда происходит такой переход, конвейер не может продолжать работу. Именно поэтому нам пришлось добавить в микрооперацию бит перехода. Когда в блок формирования очереди поступает микрооперация с таким битом, блок воздерживается от передачи сигнала о получении данных блоку декодирования. В результате машина простаивает до тех пор, пока этот переход не разрешится.
Предположительно, некоторые IJVM-команды, не зависящие от этого перехода, могут быть уже переданы в блок декодирования, но не в блок формирования очереди, поскольку он еще не выдал сигнал о получении. Чтобы разобраться в этой путанице и вернуться к нормальной работе, требуется специальное устройство и особые механизмы, но мы не будем рассматривать их в этой книге. Здесь отметим только, что классик, написавший о губительности команд goto, был безусловно прав [55].
Мы начали с микроархитектуры Mic-1 и, пройдя довольно долгий путь, закончили микроархитектурой Mic-4. Аппаратное обеспечение микроархитектуры Mic-1 оказалось очень простым, поскольку практически все управление было реализовано программно. Микроархитектура Mic-4 является конвейеризированной структурой с семью ступенями и более сложным аппаратным обеспечением. Данный конвейер изображен на рис. 4.24. Цифры в кружочках соответствуют компонентам на рис. 4.23. В микроархитектуре Mic-4 поток байтов заранее вызывается из памяти в автоматическом режиме, декодируется в IJVM-команды, которые затем с помощью ПЗУ превращаются в последовательность операций и применяются по назначению. Первые три ступени конвейера при желании можно связать с задающим генератором тракта данных, но работа будет происходить не в каждом цикле. Например, блок выборки команд совершенно точно не сможет передавать новый код операции блоку декодирования в каждом цикле, поскольку выполнение IJVM-команды занимает несколько циклов и очередь быстро переполнится.
В каждом цикле значения регистров MIR смещаются, и микрооперация, находящаяся в начале очереди, копируется в регистр MIR1. Затем сигналы управления от всех четырех регистров MIR передаются по тракту данных, вызывая определенные действия. Каждый регистр MIR контролирует отдельную часть тракта данных и, следовательно, разные микрошаги.
В данной разработке имеется конвейеризированный процессор. Благодаря этому отдельные шаги становятся очень короткими, а тактовая частота — высокой. Многие процессоры проектируются именно таким образом, особенно те, которым приходится выполнять устаревший набор команд (CISC). Например, процессор Pentium II в некоторых аспектах сходен с микроархитектурой Mic-4, как мы увидим позднее в этой главе.
Повышение производительности
Все производители компьютеров хотят, чтобы их системы работали как можно быстрее. В этом разделе мы рассмотрим ряд передовых технологий повышения производительности системы (в первую очередь — процессора и памяти), которые исследуются в настоящее время. Поскольку в компьютерной индустрии конкуренция очень острая, между появлением новой идеи о повышении скорости работы компьютера и воплощением этой идеи обычно проходит очень немного времени. Следовательно, большинство идей, которые мы сейчас будем обсуждать, вероятнее всего уже применяются в производстве.
Рассматриваемые усовершенствования можно разделить на две категории, касающиеся реализации и архитектуры. Усовершенствования реализации — это такие способы построения новых процессора и памяти, после применения которых система работает быстрее, но архитектура при этом не меняется. Изменение реализации без изменения архитектуры означает, что устаревшие программы смогут работать на новой машине, а это очень важно для успешной продажи. Чтобы усовершенствовать реализацию, можно, например, использовать более быстрый задающий генератор, но это — не единственный способ. Отметим, что рост производительности от процессора 80386 к процессорам 80486, Pentium, Pentium Pro, а затем и к Pentium II, достигался без изменения архитектуры.
Однако некоторые варианты усовершенствований можно реализовать только путем изменения архитектуры. Иногда, например, нужно добавить новые команды или регистры, причем таким образом, чтобы устаревшие программы могли работать на новых моделях. В этом случае для достижения максимальной производительности программное обеспечение приходится переделывать или, по крайней мере, заново компилировать на новом компиляторе.
Однако один раз в несколько десятилетий разработчики понимают, что старая архитектура уже никуда не годится и единственный способ развивать технологии дальше — начать все заново. Таким революционным скачком было появление в 80-х годах RISC-архитектуры, а сейчас уже приближается следующий прорыв. Мы рассмотрим соответствующий пример (Intel IA-64) в главе 5.
Далее в этом разделе мы расскажем о четырех приемах повышения производительности процессора. Начнем мы с трех хорошо зарекомендовавших себя вариантов усовершенствования реализации, а затем перейдем к варианту, требующему незначительного усовершенствования архитектуры. Указанные приемы касаются кэш-памяти, прогнозирования ветвлений, выполнения с изменением очередности и подменой регистров, а также спекулятивного выполнения.
Кэш-память
Одним из самых важных вопросов при разработке компьютеров была и остается организация системы памяти, поддерживающей передачу операндов процессору с той же скоростью, с которой он их обрабатывает. Стремительное увеличение быстродействия процессора, к сожалению, не сопровождается столь же стремительным повышением скорости работы памяти. Относительно процессора память работает все медленнее и медленнее. Учитывая чрезвычайную важность основной памяти, эта ситуация, которая ухудшается с каждым годом, серьезно тормозит развитие высокопроизводительных систем, заставляя разработчиков искать обходные пути.
Современные процессоры предъявляют определенные требования к системе памяти и в плане времени ожидания (задержки в доставке операнда), и в плане пропускной способности (объему данных, передаваемых в единицу времени). К сожалению, эти два аспекта системы памяти в значительной степени противоречивы. Обычно с повышением пропускной способности увеличивается время ожидания. Например, конвейерные технологии, которые мы использовали в микроархитектуре Mic-З, можно применить и к системе памяти, в этом случае запросы к памяти будут обрабатываться более рационально, с перекрытием. Однако, к сожалению, как и в микроархитектуре Mic-З, это приводит к увеличению времени ожидания отдельных операций памяти. С увеличением скорости работы задающего генератора становится все сложнее поддерживать такую систему памяти, которая могла бы передавать операнды за один или два цикла.
Один из вариантов решения проблемы — добавление кэш-памяти. Как мы отмечали в подразделе «Кэш-память» раздела «Основная память» главы 2, в кэш-памяти хранятся наиболее часто используемые слова, за счет чего повышается скорость доступа к ним. Если достаточно большой процент нужных слов находится в кэш-памяти, время ожидания может значительно сократиться.
Одной из самых эффективных технологий одновременного увеличения пропускной способности и уменьшения времени ожидания является применение нескольких блоков кэш-памяти. Основной прием — введение отдельных кэшей для команд и данных (так называемая разделенная кэш-память). Такая кэш-память имеет несколько преимуществ, в частности, операции могут начинаться независимо в каждой кэш-памяти, что удваивает пропускную способность системы памяти. Именно по этой причине в микроархитектуре Mic-1 нам понадобились два отдельных порта памяти: отдельный порт для каждого кэша. Отметим, что каждый кэш имеет независимый доступ к основной памяти.
В настоящее время многие системы памяти гораздо сложнее этих. Между разделенной кэш-памятью и основной памятью часто помещается кэш-память второго уровня. Вообще говоря, поскольку требуются более совершенные системы, может быть три и более уровня кэш-памяти. На рис. 4.25 изображена система с тремя уровнями кэш-памяти. Прямо на микросхеме центрального процессора находится небольшой кэш для команд (L1-I) и небольшой кэш для данных (L1-D) объемом обычно от 16 до 64 Кбайт. Есть еще кэш-память второго уровня (L2), которая расположена не на самой микросхеме процессора, а рядом с ним в том же блоке. Кэш-память второго уровня соединяется с процессором через высокоскоростной тракт данных. Эта кэш-память обычно не является разделенной и объединяет данные и команды. Ее размер — от 512 Кбайт до 1 Мбайт. Кэш-память третьего уровня (L3) находится на той же плате, что и процессор, и обычно состоит из статического ОЗУ в несколько мегабайтов, которое функционирует гораздо быстрее, чем динамическое ОЗУ основной памяти. Как правило, все содержимое кэш-памяти первого уровня находится в кэш-памяти второго уровня, а все содержимое кэш-памяти второго уровня — в кэш-памяти третьего уровня.
Существует два варианта локализации адресов, от которых зависит работа кэш-памяти. Пространственная локализация основана на вероятности того, что в скором времени появится потребность обратиться к ячейкам памяти, расположенным рядом с недавно вызванными ячейками. Исходя из этого наблюдения, в кэш-память переносится больше данных, чем требуется в данный момент. Временная локализация имеет место, когда недавно вызванные ячейки запрашиваются снова. Это может происходить, например, с ячейками памяти, находящимися рядом с вершиной стека, или с командами внутри цикла. Принцип временной локализации используется при выборе элементов, которые следует удалить из кэш-памяти в случае кэш-промаха. Обычно удаляются те элементы, к которым давно не было обращений.
Во всех типах кэш-памяти используется следующая модель. Основная память разделяется на блоки фиксированного размера, которые называются строками кэша. Строка кэша состоит из нескольких последовательных байтов (обычно от 4 до 64). Строки нумеруются, начиная с 0, то есть если размер строки составляет 32 байта, то строка 0 — это байты с 0 по 31-й, строка 1 — байты с 32-го по 63-й и т. д. В любой момент в кэш-памяти находится несколько строк. Когда происходит обращение к памяти, контроллер кэш-памяти проверяет, есть ли нужное слово в кэш-памяти. Если слово есть (случай кэш-попадания), то можно сэкономить время, требуемое на доступ к основной памяти. Если данного слова в кэш-памяти нет (случай кэш-промаха), то одна из строк из кэша удаляется, а вместо нее туда помещается запрошенная строка из основной памяти или из кэш-памяти более низкого уровня. Существует множество вариаций данной схемы, но в их основе всегда лежит идея держать в кэш-памяти как можно больше часто используемых строк, чтобы число кэш-попаданий было максимальным.
Кэш-память прямого отображения
Самый простой тип кэш-памяти — кэш-память прямого отображения. Пример одноуровневой кэш-памяти прямого отображения показан на рис. 4.26, а. Данная кэш-память содержит 2048 элементов. Каждый элемент (ряд) может вмещать ровно одну сроку из основной памяти. Если размер строки кэша составляет 32 байта (как в этом примере), кэш-память может вмещать 64 Кбайт. Каждый элемент кэш-памяти состоит из трех частей:
+ Бит достоверности указывает, есть достоверные данные в элементе или нет. Когда система загружается, все элементы маркируются как недостоверные.
+ Поле тега состоит из уникального 16-разрядного значения, указывающего соответствующую строку памяти, из которой поступили данные.
+ Поле данных содержит копию данных памяти. Это поле вмещает одну строку кэша размером 32 байта.
В кэш-памяти прямого отображения заданное слово может храниться только в одном месте. Если его в этом месте нет, значит, его вообще нет в кэш-памяти. Для хранения данных в кэше и извлечения их из кэша адрес разбивается на 4 компонента, как показано на рис. 4.26, б\
+ Поле строки указывает, какой элемент кэш-памяти содержит соответствующие данные, если они есть в кэш-памяти.
б
Рис.
4.26. Кэш-память
прямого отображения (а); 32-разрядный
виртуальный адрес (б)
♦ Поле слова указывает, на какое слово в строке производится ссылка.
+ Поле байта обычно не используется, но если требуется только один байт, в этом поле указано, какой именно байт в слове нужен. Для кэш-памяти, поддерживающей только 32-разрядные слова, это поле всегда содержит 0.
Когда центральный процессор выдает адрес памяти, аппаратура выделяет из этого адреса 11 бит поля строки и использует их для поиска в кэш-памяти одного из 2048 элементов. Если элемент действителен, то производится сравнение поля тега основной памяти и поля тега кэш-памяти. Если поля равны, значит, в кэш-памяти есть запрашиваемое слово. Такая ситуация называется кэш-попа- данием. В случае кэш-попадания слово берется прямо из кэш-памяти, и тогда не нужно обращаться к основной памяти. Из элемента кэш-памяти берется только нужное слово, остальная часть элемента не используется. Если элемент кэш-па- мяти недействителен (недостоверен) или поля тега не совпадают, то нужного слова нет в памяти. Такая ситуация называется кэш-промахом. В этом случае 32-байтная строка вызывается из основной памяти и сохраняется в кэш-памяти, заменяя тот элемент, который там был. Однако если существующий элемент кэш-памяти изменяется, его нужно записать обратно в основную память до того, как он будет удален.
Несмотря на сложность решения, доступ к нужному слову может быть чрезвычайно быстрым. Поскольку известен адрес, известно и точное местоположение слова, если оно имеется в кэш-памяти. Это значит, что нужно считать слово из кэш-памяти, доставить его процессору и одновременно с этим проверить, правильное ли это слово (путем сравнения полей тега). Поэтому процессор в действительности получает слово из кэш-памяти одновременно или даже до того, как становится известно, запрошенное это слово или нет.
При такой схеме смежные строки основной памяти помещаются в смежные элементы кэш-памяти. Фактически в кэш-памяти может храниться до 64 Кбайт смежных данных. Однако две строки, адреса которых отличаются ровно на 64 Кбайт (65 536 байт) или на любое целое, кратное этому числу, не могут одновременно хранится в кэш-памяти (поскольку они имеют одно и то же значение поля строки). Например, если программа обращается к данным с адресом X, а затем выполняет команду, которой требуются данные с адресом X + 65 536 (или с любым другим адресом в той же строке), вторая команда требует перезагрузки элемента кэш-памяти. Если это происходит достаточно часто, то могут возникнуть проблемы. В действительности, если кэш-память плохо работает, лучше, чтобы ее вообще не было, поскольку при каждой операции с основной памятью считывается целая строка, а не одно слово.
Кэш-память прямого отображения — это самый распространенный тип кэш-памяти, и она достаточно эффективна, поскольку коллизии, подобные описанной, случаются крайне редко или вообще не случаются1. Например, качественный компилятор может учитывать подобные коллизии при размещении команд и данных в памяти. Отметим, что указанный случай не произойдет в системе, где команды и данные хранятся раздельно, поскольку конфликтующие запросы будут обслуживаться разными кэшами. Таким образом, мы видим второе преимущество наличия двух кэшей вместо одного — больше гибкости при разрешении конфликтных ситуаций.
Ассоциативная кэш-память с множественным доступом
Как было отмечено ранее, различные строки основной памяти конкурируют за право занять одну и ту же область кэша. Если программе, использующей кэш-па- мять, изображенную на рис. 4.26, а, часто требуются слова с адресами 0 и 65 536, то будут иметь место постоянные конфликты, поскольку каждое обращение потенциально повлечет за собой вытеснение из кэш-памяти той или иной строки. Чтобы разрешить эту проблему, нужно сделать так, чтобы в каждом элементе кэш-памяти помещалось по две и более строки. Кэш-память с п возможными элементами для каждого адреса называется n-входовой ассоциативной кэш-па- мятью. 4-входовая ассоциативная кэш-память изображена на рис. 4.27.