Шапка

9.4 Предел пропускной способности памяти

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

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

9.4.1 История проблемы

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

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

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

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

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

9.4.2 Текущие проблемы производительности памяти

В новом поколении компьютеров появилась новая проблема. Быстродействие микросхем кэш-памяти перестало удовлетворять требованиям процессоров. Это произошло, не потому что транзисторы в процессоре начали переключаться быстрее, чем в микросхемах памяти ( это не так ). Проблема в том, что в цикле обмена стали доминировать времена распространения сигналов через контакты микросхем.

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

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

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

9.4.3 «Стековый» путь решения проблемы

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

Особенностью стековых машин является невозможность использования в них обычного аппаратного кэша. Вместо него для быстрого исполнения избранных процедур должна использоваться распределяемая статически или управляемая операционной системой быстрая программная память [* или, иначе говоря, обычные кэш-схемы в них не работают ] . В таких областях можно разместить активно используемый код, что позволит свободнее использовать программную память компилятором и программистом. Из-за того, что код для стековых процессоров очень компактен, во внутренней памяти процессора можно разместить заметную часть программы, дополнительно стимулируя модульность и повторное использование процедур. Такой подход увеличивает производительность, а не снижает её, как в машинах других архитектур. Из-за того, что внутрипроцессорная память не нуждается в сложной схеме управления кэшем, на всей остающейся площади кристалла можно разместить дополнительную программную память. Для 16-разрядных моделей было бы удобно хранить там всю программу и память данных для переменных. С развитием технологий до субмикронных уровней то же станет возможно и для 32-разрядных процессоров.

Чтобы разобраться, как работает альтернативная иерархия памяти, рассмотрим машину с микрокодированными инструкциями, подобную RTX 32P . Во внешних микросхемах динамической памяти может храниться основной объём программы и данных. Это, на самом деле, нетипичная ситуация, потому что программы для RTX 32P редко требуют больше места, чем объём микросхем его статической памяти, но предположим, что всё так и есть. Микросхемы динамической памяти образуют хранилище для редко исполняемых уровней программы и для данных, нужных нечасто.

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

Следующим уровнем идёт программная память внутри процессора. В ней может размещаться код внутренних циклов или процедур, требующих быстрого доступа. Туда легко поместятся несколько сотен байт оперативной памяти для данных и команд [* старые добрые времена, когда транзисторы были большими ] . В том случае, когда машина предназначена для исполнения какой-либо конкретной программы ( что для встраиваемых систем является основным вариантом использования ), на кристалле можно организовать ROM объёмом несколько тысяч байт. На практике библиотечные функции для любого языка могут помещаться в ROM для удобства компиляторов и программистов.

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

Итак, мы имеем многоуровневую структуру памяти различного быстродействия и размера, пронизывающую всю конструкцию. Концепция иерархичности не нова. Новой является идея ненужности аппаратного управления памятью в момент выполнения программы. С этим легко справится компилятор и программист. В основе этой концепции лежит возможность статического распределения кода между различными уровнями. Чрезвычайно быстрые вызовы подпрограмм позволяют сохранять в высокоскоростной памяти только внутренние циклы и часто исполняемые фрагменты кода, а не приложение целиком. Это означает, что динамическое распределение памяти на самом деле не нужно. [* Непонятно как из тезисов данного абзаца получается такой вывод. Если в значении «можно обойтись», то да, можно. Но это подходит не для всех задач ] .

Previous part:

Next part: