Boroda_2
.docБолее того, минимизация размера команд может усложнить их декодирование и перекрытие. Следовательно, стремление уменьшить размер команд должно уравновешиваться стремлением сократить время их декодирования и выполнения.
Есть еще одна очень важная причина минимизации длины команд, и она становится все важнее с увеличением скорости работы процессоров: пропускная способность памяти (число битов в секунду, которое память может предоставлять). Значительное повышение быстродействия процессоров за последнее десятилетие не соответствует увеличению пропускной способности памяти. Ограничения здесь связаны с неспособностью системы памяти передавать команды и операнды с той же скоростью, с которой процессор может их обрабатывать. Пропускная способность памяти зависит от технологии разработки. Встречаемые на этом пути трудности имеют отношение не только к основной памяти, но и ко всем видам кэш-памяти.
Если пропускная способность кэш-памяти команд составляет t бит/с, а средняя длина команды — г бит, то кэш-память способна передавать самое большее t/r команд в секунду. Отметим, что это — верхний предел скорости, с которой процессор может выполнять команды, хотя в настоящее время предпринимаются попытки преодолеть данный барьер. Ясно, что скорость, с которой могут выполняться команды (то есть быстродействие процессора), может ограничиваться длиной команд. Чем короче команды, тем быстрее работает процессор. Поскольку современные процессоры способны выполнять несколько команд за один цикл, то вызов нескольких команд за цикл обязателен. Этот аспект применения кэш-памяти команд делает размер команд важным критерием, который нужно учитывать при разработке.
Еще один критерий — достаточный объем пространства в формате команды для представления всех требуемых операндов. Машина, поддерживающая 2” операций и длину команды менее п бит, невозможна. В этом случае в коде операции было бы недостаточно места для того, чтобы указать, какая нужна команда. К тому же история снова и снова доказывает, что обязательно нужно оставлять большое количество свободных кодов операций для будущих дополнений набора команд.
Третий критерий связан с числом битов в адресном поле. Рассмотрим проект машины с 8-разрядными символами и основной памятью, которая должна содержать 232 символов. Разработчики вольны были приписать последовательные адреса блокам по 8, 16, 24 или 32 бита.
Представим, что бы случилось, если бы команда разработчиков разбилась на две воюющие группы, одна из которых утверждает, что основной единицей памяти должен быть 8-разрядный байт, а другая требует, чтобы основной единицей памяти было 32-разрядное слово. Первая группа предложила бы память из 232 байт с номерами 0, 1, 2, 3,4 294 967 295. Вторая группа предложила бы память из 230 слов с номерами 0, 1, 2, 3, ..., 1 073 741 823.
Первая группа скажет, что для того, чтобы сравнить два символа при организации по 32-разрядным словам, программе приходится не только вызывать из памяти слова, содержащие эти символы, но и выделять соответствующий символ из каждого слова для сравнения. А это потребует дополнительных команд и, следовательно, дополнительного пространства. 8-разрядная организация, напротив, обеспечивает адресацию каждого символа, что значительно упрощает процедуру сравнения.
Сторонники 32-разрядной организации скажут, что их проект требует всего лишь 230 отдельных адресов, что дает длину адреса всего 30 бит, тогда как при 8-разрядной организации требуется целых 32 бита для обращения к той же самой памяти. Если адрес короткий, то и команда будет более короткой. Она займет меньше пространства в памяти, и к тому же для ее вызова потребуется меньше времени. В качестве альтернативы они могут сохранить 32-разрядный адрес для обращения к памяти в 16 Гбайт вместо каких-то там 4 Гбайт.
Этот пример демонстрирует, что для получения оптимальной дискретности памяти требуются более длинные адреса и, следовательно, более длинные команды. Одна крайность — это организация памяти, при которой адресуется каждый бит (например, Burroughs В1700). Другая крайность — это память, состоящая из очень длинных слов (например, серия CDC Cyber содержала 60-разряд- ные слова).
Современные компьютерные системы пришли к компромиссу, который, в каком-то смысле, объединил в себе худшие качества обоих вариантов. Они требуют, чтобы адреса были у отдельных байтов, но при обращении к памяти всегда считываются одно, два, а иногда даже четыре слова сразу. В результате считывания одного байта из памяти на машине UltraSPARC III единовременно вызываются минимум 16 байт (см. рис. 3.44), а иногда и вся строка кэш-памяти размером 64 байта.
Расширение кода операций
В предыдущем подразделе мы увидели, что короткие адреса препятствуют удачной дискретности памяти. В этом разделе мы рассмотрим компромиссы, связанные с кодами операций и адресами. Рассмотрим команду размером п + k бит с кодом операции в k бит и одним адресом в п бит. Такая команда допускает 2k различных операций и 2п адресуемых ячеек памяти. В качестве альтернативы те же п + k бит можно разбить на код операции в k - 1 бит и адрес в п + 1 бит. При этом будет либо в два раза меньше команд, но в два раза больше памяти, либо то же количество памяти, но дискретность вдвое выше. Код операции в k + 1 бит и адрес в п - 1 бит дает большее количество операций, но ценой этого является либо меньшее количество ячеек памяти, либо не очень удачная дискретность при том же объеме памяти. Наряду с подобными простыми компромиссами между битами кода операции и битами адреса существуют и более сложные. Обсуждаемый здесь механизм называется расширением кода операций.
Рис. 5.8. Команда с 4-разрядным кодом операции и тремя 4-разрядными
адресными полями
Если разработчикам нужно 15 трехадресных команд, 14 двухадресных команд, 31 одноадресная команда и 16 безадресных команд, они могут использовать коды операций от 0 до 14 в качестве трехадресных команд, а код операции 15 уже интерпретировать по-другому (рис. 5.9).
Это значит, что код операции 15 содержится в битах с 8-го по 15-й, а не с 12-го по 15-й. Биты с 0-го по 3-й и с 4-го по 7-й, как и раньше, формируют два адреса. Все 14 двухадресных команд содержат число 1111 в старших четырех битах и числа от 0000 до 1101 в битах с 8-го по 11-й. Команды с числом 1111 в старших четырех битах и числом 1110 или 1111 в битах с 8-го по 11-й рассматриваются особо. Они трактуются так, как будто их коды операций находятся в битах с 4-го по 15-й. В результате получаем 32 новых кода операций. А поскольку требуется всего 31 код, то код 111111111111 означает, что действительный код операции находится в битах с 0-го по 15-й, что дает 16 безадресных команд.
Как видим, код операции становится все длиннее и длиннее: трехадресные команды имеют 4-разрядный код операции, двухадресные команды — 8-разряд- ный, одноадресные команды — 12-разрядный, а безадресные команды — 16-раз - рядный.
Идея расширения кода операций наглядно демонстрирует компромисс между пространством для кодов операций и пространством для другой информации. Однако на практике все не так просто и понятно, как в нашем примере. Есть только два способа изменения размера кода операций. С одной стороны, можно иметь все команды одинаковой длины, приписывая самые короткие коды операций тем командам, которым нужно больше всего битов для спецификации чего-либо другого. С другой стороны, можно свести к минимуму средний размер команды, если выбрать самые короткие коды операций для часто используемых команд и самые длинные — для редко используемых.
Если довести эту идею до конца, можно свести к минимуму среднюю длину команды, закодировав каждую команду, чтобы максимально уменьшить число требуемых битов. К сожалению, это ведет к наличию команд разных размеров, причем не выровненных в границах байтов. Пока существуют архитектуры команд с таким свойством (например, Intel 432), выравнивание будет иметь большое значение для быстрого декодирования команд. Тем не менее, эта идея часто применяется на уровне байтов. Далее мы рассмотрим архитектуру JVM-команд, чтобы показать, как можно менять форматы команд, чтобы максимально уменьшить размер программы.
Форматы команд процессора Pentium 4
Форматы команд процессора Pentium 4 очень сложны и нерегулярны. Они содержат до шести полей разной длины, пять из которых не обязательны (общая модель показана на рис. 5.10). Такая ситуация сложилась из-за того, что архитектура развивалась на протяжении нескольких поколений, и ранее в нее были включены не очень удачные варианты команд. Из-за требования обратной совместимости позднее их нельзя было изменить. Например, если один из операндов команды находится в памяти, то другой не может там находиться. В результате, существуют команды сложения двух регистров, команды прибавления регистра к слову памяти, команды прибавления слова памяти к регистру, но не существует команд для прибавления одного слова памяти к другому слову памяти.
В первых архитектурах Intel все коды операций были размером 1 байт, хотя для изменения некоторых команд часто использовался так называемый пре фиксный байт. Префиксный байт — это дополнительный код операции, который ставится перед командой, чтобы изменить ее действие. Примером применения префиксного байта может служить команда WIDE в машинах IJVM. К сожалению, в какой-то момент компания Intel исчерпала запасы кодов операций, и один код операции, OxFF, стал кодом смены алфавита и использоваться для разрешения второго байта команды.
Index, Base — масштаб, индекс, база), который дает дополнительную спецификацию. Эта схема не идеальна, она является компромиссом между требованием обратной совместимости и желанием учесть новые особенности, которые не были предусмотрены изначально.
Добавим еще, что некоторые команды имеют 1, 2 или 4 дополнительных байта для определения адреса команды (смещение), а иногда еще 1, 2 или 4 байта, содержащих константу (непосредственный операнд).
Форматы команд процессора UltraSPARC III
Архитектура команд процессора UltraSPARC III состоит из 32-разрядных команд, выровненных в памяти. Команды очень просты. Каждая из них выполняет только одно действие. Типичная команда задает два регистра, в которых находятся исходные операнды, и один выходной регистр. Вместо одного из регистров команда может использовать константу со знаком. При выполнении команды LOAD два регистра (или один регистр и 13-разрядная константа) складываются вместе для определения считываемого адреса памяти. Данные оттуда записываются в другой указанный в команде регистр.
Изначально машина SPARC имела ограниченное число форматов команд (рис. 5.11). Со временем добавлялись новые форматы. Когда писалась эта книга, количество форматов уже было равно 31, и в планах было увеличение (долго ли осталось ждать того дня, когда появится реклама «самого сложного в мире RISC-процессора»?). Большинство новых вариантов были получены за счет отъема нескольких битов у какого-нибудь поля. Например, изначально для команд перехода использовался вариант 3 формата (с 22-разрядным смещением). Когда были добавлены команды прогнозируемых ветвлений, из 22 бит было изъято 3: один из них стал использоваться для прогнозирования (совершать или не совершать переход), а два оставшихся определяли, какой набор битов условного кода нужно использовать. В результате получилось 19-разрядное смещение. Приведем другой пример. Существует много команд преобразования типов данных (целых чисел — в числа с плавающей точкой и т. д.). Для большинства этих команд используется вариант lb формата, в котором поле непосредственной константы разбито на 5-разрядное поле, указывающее входной регистр, и 8-разрядное поле, предоставляющее дополнительные биты кода операции. Однако в большинстве команд все еще используются форматы, показанные на рисунке.
Первые 2 бита каждой команды помогают определить формат команды и сообщают аппаратному обеспечению, где найти оставшуюся часть кода операции, если она есть. В варианте 1а формата оба источника операндов представляют собой регистры; в варианте lb один источник — регистр, второй — константа в промежутке от -4096 до +4095. Бит 13 определяет один из этих двух форматов. (Биты нумеруются с 0.) В обоих случаях местом сохранения результатов всегда является регистр. Достаточный объем пространства имеется для 64 команд, некоторые из которых зарезервированы на будущее.
Поскольку все команды 32-разрядные, включить в команду 32-разрядную константу невозможно. Команда SETHI устанавливает 22 бита, оставляя пространство для другой команды, чтобы установить оставшиеся 10 бит. Это единственная команда такого необычного формата.
Рис. 5.11. Исходные форматы команд процессора SPARC
Для непрогнозируемых условных переходов используется вариант 3 формата, в котором поле условия определяет, что проверяется. Бит А нужен для того, чтобы избегать пустых операций при определенных условиях. Формат команд прогнозируемых ветвлений тот же, только с 19-разрядным смещением, как было сказано ранее.
Последний формат используется для команды вызова процедуры CALL. Эта команда особая, поскольку только в ней для определения адреса требуется 30 бит. В данной архитектуре существует один 2-разрядный код операции. Требуемый адрес — это целевой адрес, разделенный на четыре. Таким образом, относительно текущей команды диапазон составляет примерно 231 байт.
Форматы команд 8051
В 8051 предусмотрено шесть простых форматов команд (рис. 5.12). Размер команд может быть равен 1, 2 или 3 байтам. Вариант 1 формата предусматривает наличие в команде только кода операции. Такова, к примеру, команда инкремента сумматора.
Адресация
Большинство команд работают с операндами, расположение которых необходимо каким-то образом указать. Этот механизм, который мы обсудим в данном разделе, называется адресацией.
Режимы адресации
До сих пор мы не рассказывали о том, как интерпретируются биты адресного поля для нахождения операнда. Самое время разобраться в этой проблеме. Итак, поговорим о режимах адресации.
Команда, построенная по варианту 6 формата, содержит два 8-разрядных операнда. Этот формат характерен для многих команд, например, для команды переноса 8-разрядной непосредственной константы по адресу встроенной в микросхему памяти.
Команда, построенная по варианту 3 формата, состоит из операнда размером 1 байт. В качестве операнда может, в частности, выступать непосредственная константа (загружаемая в сумматор), смещение (например расстояние перехода) или номер бита (в целях установки, сброса или проверки бита п).
Варианты 4 и 5 формата предназначены для команд переходов и вызовов подпрограмм. 11-разрядные адреса применяются в отсутствие внешней памяти, когда длина адреса не превышает 4096 (в модели 8051) или 8192 (в модели 8052). Если внешняя память присутствует и ее объем составляет более 8 Кбайт, применяются 16-разрядные адреса.
Непосредственная адресация
Самый простой способ указания операнда — хранить в адресной части сам операнд, а не адрес операнда или какую-либо другую информацию, описывающую, где находится операнд. Такой операнд называется непосредственным, поскольку он автоматически вызывается из памяти одновременно с командой; следовательно, сразу становится непосредственно доступным. Один из вариантов команды с непосредственным адресом для загрузки в регистр R1 константы 4 показан на рис. 5.13.
При непосредственной адресации не требуется дополнительного обращения к памяти для вызова операнда. Однако у такого способа адресации есть недостатки. Во-первых, таким способом можно работать только с константами. Во-вторых, число значений ограничено размером поля. Тем не менее эта технология используется во многих архитектурах для определения целочисленных констант.
Прямая адресация
Следующий способ определения операнда — просто дать его полный адрес. Такой режим называется прямой адресацией. Как и непосредственная адресация, прямая адресация имеет некоторые ограничения: команда всегда имеет доступ только к одному и тому же адресу памяти. То есть значение может меняться, а адрес — нет. Таким образом, прямая адресация может использоваться только для доступа к глобальным переменным, адреса которых известны во время компиляции. Многие программы содержат глобальные переменные, поэтому этот способ широко используется. Каким образом компьютер узнает, какие адреса непосредственные, а какие прямые, мы обсудим позже.
Регистровая адресация
Регистровая адресация по сути напоминает прямую, только в данном случае вместо ячейки памяти указывается регистр. Поскольку регистры очень важны (благодаря быстрому доступу и коротким адресам), этот режим адресации является самым распространенным на большинстве компьютеров. Многие компиляторы определяют, к каким переменным доступ будет осуществляться чаще всего (например, индексы циклов) и помещают эти переменные в регистры.
Такой режим называют регистровой адресацией. В архитектурах с перенаправлением для загрузки, например UltraSPARC II, практически все команды используют этот режим адресации. Он не применяется только в том случае, если операнд перемещается из памяти в регистр (команда LOAD) или из регистра в память (команда STORE). Но даже в этих командах один из операндов является регистром — туда отправляется слово из памяти и оттуда перемещается слово в память.
Косвенная регистровая адресация
При косвенной регистровой адресации искомый операнд берется из памяти или отправляется в память, но адрес не фиксируется жестко в команде, как при прямой адресации, а находится в регистре. Если адрес используется таким образом, он называется указателем. Преимущество косвенной адресации состоит в том, что можно обращаться к памяти, не имея в команде полного адреса. Кроме того, многократно выполняя данную команду, можно, меняя значение в регистре, использовать разные слова памяти.
Чтобы понять, почему может быть полезно использовать разные слова при каждом выполнении команды, представим себе цикл, который проходит по 1024-элементному одномерному массиву целых чисел для получения в регистре R1 суммы элементов. Вне этого цикла какой-то другой регистр, например R2, может указывать на первый элемент массива, а еще один регистр, например R3, — на первый адрес после массива. Массив содержит 1024 целых числа по 4 байта каждое. Если массив начинается с элемента Д то первый адрес после массива будет А + 4096. Типичная программа на ассемблере, выполняющая это вычисление для двухадресной машины, показана в листинге 5.1.
MOV
R1,#0
MOV
R2JA MOV R3JA+4096 LOOP: ADD R1.(R2)
ADD
R2,#4 CMP R2,R3 BLT LOOP
увеличение R2 на одно слово (4 байта)
проверка завершения
если R2 < R3, продолжать цикл
В этой маленькой программе мы использовали несколько режимов адресации. В первых трех командах выполняется регистровая адресация первого операнда (целевого) и непосредственная адресация второго (константа, обозначаемая символом #). Вторая команда помещает в R2 не содержимое элемента А, а элемент А. Именно это и сообщает ассемблеру знак #. Сходным образом третья команда помещает в R3 первое слово после массива.
Интересно отметить, что само тело цикла не содержит каких-либо адресов памяти. В четвертой команде используются регистровая и косвенная адресация. В пятой команде применяются регистровая и непосредственная адресация, в шестой — оба раза регистровая. Команда BLT могла бы использовать адрес памяти, однако более привлекательным является определение адреса с помощью 8-раз- рядного смещения, связанного с самой командой BLT. Таким образом, вообще без обращения по адресам памяти, мы получили короткий и быстрый цикл. Кстати, эта программа предназначена для Pentium 4, только мы переименовали команды и регистры и для простоты изменили запись.
Теоретически есть еще один способ выполнения этого фрагмента без косвенной регистровой адресации. Цикл мог бы содержать команду для прибавления А к регистру R1, например:
ADD R1, А
Тогда при каждом шаге команда должна увеличиваться на 4. Таким образом, после одного шага команда будет выглядеть следующим образом:
ADD R1, А + 4
И далее аналогично до завершения цикла.
Программа, которая сама изменяет себя подобным образом, называется само- модифицирующейся программой. Идея, предложенная еще Джоном фон Нейманом, применялась в старых компьютерах, которые не поддерживали режим косвенной регистровой адресации. В настоящее время самомодифицирующиеся программы считаются неудобными и трудными для понимания. Кроме того, их нельзя выполнять совместно несколькими процессорами. Они не могут правильно выполняться даже на машинах с разделенной кэш-памятыо первого уровня, если в кэш-памяти команд нет специальной схемы для обратной записи (поскольку разработчики предполагали, что программы сами себя изменять не должны).
Индексная адресация
Часто нужно уметь обращаться к словам памяти по известному смещению. Подобные примеры мы видели в машине IJVM, где локальные переменные определяются по смещению от регистра LV. Обращение к памяти по регистру и константе смещения называется индексной адресацией.
В машине IJVM при доступе к локальной переменной используется указатель ячейки памяти (LV) в регистре плюс небольшое смещение в самой команде, как показано на рис. 4.14, а. Есть и другой способ: указатель ячейки памяти в команде и небольшое смещение в регистре. Чтобы показать, как работает этот механизм, рассмотрим следующий пример. Пусть у нас есть два одномерных массива А и В по 1024 слова в каждом. Нам нужно вычислить Д И В{ для всех пар, а затем соединить все эти 1024 логических произведения операцией ИЛИ, чтобы узнать, есть ли в этом наборе хотя бы одна пара, не равная нулю. Один из вариантов — поместить адрес массива А в один регистр, а адрес массива В — в другой регистр, а затем последовательно перебирать элементы массивов аналогично тому, как мы делали в предыдущей программе (см. листинг 5.1). Такая программа, конечно же, будет работать, но ее молено усовершенствовать, как показано в листинге 5.2.
Листинг 5.2. Программа на ассемблере, выполняющая операцию ИЛИ для 1024 элементов массива
MOV R1,#0 |
собирает результаты выполнения ИЛИ в R1 |
|
изначально 0 |
MOV R2,#0 |
R2 = индекс, i от текущего |
|
произведения A[i] И B[i] |
MOV R3,#4096 |
R3 = первое ненужное значение индекса |
MOV R4,A(R2) ; |
; R4 = A[i] |
AND R4,B(R2) ; |
; R4 = А[i] И B[i] |
OR R1.R4 |
|
ADD R2,#4 |
1=1+4 |
CMP R2.R3 |
нужно ли продолжать? |
BLT LOOP |
если R2 < R3, нужно продолжать |
Здесь нам требуется 4 регистра:
+ R1 — содержит результаты суммирования логических произведений;
+ R2 — индекс г, который используется для перебора элементов массива;
+ R3 — константа 4096 (это самое маленькое значение г, которое не используется);
+ R4 — временный регистр для хранения каждого произведения.
После инициализации регистров мы входим в цикл из шести команд. Команда с меткой LOOP вызывает элемент Д в регистр R4. При вычислении источника здесь используется индексная адресация. Регистр (R2) и константа (адрес элемента А) складываются, полученный результат служит для обращения к памяти. Сумма этих двух величин поступает в память, но не сохраняется ни в одном из доступных пользователю регистров. Следующая запись означает, что для определения приемника используется регистровая адресация, а для определения источника — индексная:
MOV R4,A(R2)
Здесь R4 — это регистр, А — смещение, R2 — регистр. Если А имеет значение, скажем, 124 300, то соответствующая машинная команда будет выглядеть так, как показано на рис. 5.14.
Рис.
5.14. Возможное представление команды
MOV
R4,
A(R2)
в
памяти