Журнал «Компьютерра» №37 - Страница 11
Условные инструкции ARM
Архитектура ARM (Advanced RISC Machines) разработана в 1983-85 годах в компании Acorn Computers. Команда Роджера Вильсона и Стива Фербера взяла за основу набора инструкций ARM некогда популярный, а ныне забытый процессор MOS Technology 6502 и снабдила его специальным четырехбитным кодом условия, которым можно было дополнить любую инструкцию.
Идея условных инструкций проста, как все гениальное: инструкция с условным кодом выполняется, только если в процессоре выставлен бит соответствующего условия. В противном случае она игнорируется. Ближайший аналог в наборе инструкций x86 - инструкции условного перехода, срабатывающие, только если в процессоре был выставлен тот или иной флаг; в архитектуре ARM подобные «условности» применимы к любой инструкции, а флаги можно определять самостоятельно. Идея в том, что в коде типа
Если (условие) то Выполнить1 иначе Выполнить 2
вместо того, чтобы записать традиционную конструкцию
1 Вычислить условие
2 Если условие выполнено, то идти к 5
3 Выполнить2
4 Идти к 6
5 Выполнить1
используя условные инструкции, можно записать
1 Вычислить условие и поставить Флаг1 по результатам вычисления
2 Выполнить1 при условии выставленного Флаг1
3 Выполнить2 при условии невыставленного Флаг1
Обратите внимание, что получившийся код не только более компактен, но и лишен одного условного и одного безусловного перехода, присутствовавших в классическом варианте, - тех самых переходов, которые обычно больно бьют по производительности конвейерных архитектур.
Еще ряд дополнений в ARM предусматривал введение инструкций, одновременно выполняющих несколько простых операций, тем самым избавляя регистры процессора от необходимости сохранять результаты промежуточных вычислений и увеличивая вычислительную плотность кода. Этот подход нетипичен для RISC-процессоров, поскольку плохо вписывается в «основную идею» их максимального упрощения, но в конечном счете он привел к тому, что процессоры Acorn при прочих равных получили большую производительность на единицу частоты. Конечно, ARM-подход тоже имеет недостатки (например, необходимость выполнять пустые инструкции), однако в общем и целом он позволяет создавать очень простые процессоры с очень хорошей производительностью.
В 1985 вышел первенец архитектуры ARM - 32-разрядный процессор ARM1; в 1986-м - первый коммерческий вариант архитектуры, процессор ARM2. ARM2 был настоящим шедевром - в его ядре насчитывалось всего 30 тысяч транзисторов (вчетверо меньше, чем в i80286, и втрое меньше, чем в MC68000); он отличался крайне низким энергопотреблением и обладал при всем при том производительностью, превосходящей производительность 286-го процессора (не говоря уже о том, что 286-й был 16-разрядным, а ARM2 - 32-разрядным процессором). Немного позже увидел свет и ARM3, в котором появилось четыре килобайта кэш-памяти, еще увеличившей производительность процессоров ARM.
Трудно сказать, ожидала ли Acorn Computers такого успеха, однако воспользовалась им в полной мере. В 1990 году Acorn, работавшая над развитием ARM уже в сотрудничестве с Apple, преобразовала подразделение, занимавшееся ARM, в отдельную фирму- Advanced RISC Machines. Результатом совместной работы стало ядро ARM6 и процессор ARM610, использовавшийся, в частности, в одном из первых КПК в мире - Apple Newton. Ядро ARM6 было по-прежнему невероятно простым (всего 35 тысяч транзисторов!), мало потребляющим и обеспечивало приличный уровень производительности. Поскольку тягаться в производительности с гораздо более сложными монстрами вроде i386 оно не могло (да и ниша высокопроизводительных вычислений была прочно занята MIPS), руководство Advanced RISC Machines избрало оригинальный способ ведения бизнеса - позиционировав ARM6 как «встраиваемое» вычислительное ядро, которое любой желающий за сравнительно небольшие деньги мог интегрировать в свои специализированные процессоры. Ядро ARM6 вышло столь удачным и так хорошо подходило для этой бизнес-модели (благодаря простоте, его можно было изготавливать даже на сильно устаревшем дешевом оборудовании), что вскоре архитектура ARM получила широчайшее распространение. Самый яркий пример подобного «гибрида» - ядро ARM7TDMI, являющееся основой для подавляющего большинства процессоров сотовых телефонов. Сегодня ARM используется в более чем 75% всех интегрированных процессоров, выпускаемых в мире, - от контроллеров жестких дисков, калькуляторов и микропроцессоров игрушек до сетевых маршрутизаторов. То есть там, где от процессора не требуется очень высокого быстродействия.
Другое направление, которым сегодня «жива» ARM, - это более производительная архитектура StrongARM, широко используемая в КПК, коммуникаторах и некоторых терминалах. StrongARM была разработана в 1995 году компанией DEC совместно с ARM; а позднее, после судебного разбирательства, - продана вместе с соответствующим подразделением корпорации Intel, которая сейчас и занимается ее развитием в виде линейки процессоров XScale.
Шаг 3. Введение конвейера
Идея конвейера, давным-давно предложенная Генри Фордом, состоит в том, что производительность цепочки последовательных действий определяется не сложностью этой цепочки, а лишь длительностью самой сложной операции. Иными словами, совершенно неважно, сколько человек занимаются производством автомобиля и как долго длится его изготовление в целом, - важно то, что если каждый человек в цепочке тратит, скажем, на свою операцию одну минуту, то с конвейера будет сходить один автомобиль в минуту, ни больше и ни меньше; независимо от того, сколько операций нужно совершить с отдельным автомобилем и сколько заняла бы его сборка одним человеком. Применительно к процессорам принцип конвейера означает, что если мы сумеем разбить выполнение машинной инструкции на несколько этапов, то тактовая частота (а вернее, скорость, с которой процессор забирает данные на исполнение и выдает результаты) будет обратно пропорциональна времени выполнения самого медленного этапа. Если это время удастся сделать достаточно малым (а чем больше этапов на конвейере, тем они короче), то мы сумеем резко повысить тактовую частоту, а значит, и производительность процессора.
Процедуру выполнения практически любой инструкции можно разбить как минимум на пять непересекающихся этапов:
Выборка инструкции (FETCH) из памяти. Из программы извлекается инструкция, которую нужно выполнить.
Декодирование инструкции (DECODE). Процессор «соображает», что от него хотят, и переправляет запрос на нужное исполнительное устройство.
Подготовка исходных данных для выполнения инструкции.
Собственно выполнение инструкции (EXECUTE).
Сохранение полученных результатов.
Конвейеризация потенциально применима к любой процессорной архитектуре, независимо от набора команд и положенных в ее основу принципов. Даже самый первый x86-процессор, Intel 8086, уже содержал своеобразный примитивный «двухстадийный конвейер» - выборка новых инструкций (FETCH) и их исполнение осуществлялись в нем независимо друг от друга. Однако реализовать что-то более сложное для CISC-процессоров оказалось трудно: декодирование неоднородных CISC-инструкций и их очень сильно различающаяся сложность привели к тому, что конвейер получается чересчур замысловатым, катастрофически усложняя процессор. Подобных трудностей у RISC-архитектуры гораздо меньше (а SPARC и MIPS, например, и вовсе были специально оптимизированы для конвейеризации), так что конвейеризированные RISC-процессоры появились на рынке много раньше, чем аналогичные x86.
Недостатки конвейера неочевидны, но, как обычно и бывает, из-за нескольких «мелочей» реализовать грамотно организованный конвейер совсем не просто. Основных проблем три.
Необходимость наличия блокировок конвейера. Дело в том, что время исполнения большинства инструкций может очень сильно варьироваться. Скажем, умножение (и тем более деление) чисел требуют (на стадии EXECUTE) нескольких тактов, а сложение или побитовые операции - одного такта; а для операций Load и Store, которые могут обращаться к разным уровням кэш-памяти или к оперативной памяти, это время вообще не определено (и может достигать сотен тактов). Соответственно, должен быть какой-то механизм, который бы «притормаживал» выборку и декодирование новых инструкций до тех пор, пока не будут завершены старые. Методов решения этой проблемы много, но их развитие приводит к одному - в процессорах прямо перед исполнительными устройствами появляются специальные блоки-диспетчеры (dispatcher), которые накапливают подготовленные к исполнению инструкции, отслеживают выполнение ранее запущенных инструкций и по мере освобождения исполнительных устройств отправляют на них новые инструкции. Даже если исполнение займет много тактов - внутренняя очередь диспетчера позволит в большинстве случаев не останавливать подготавливающий все новые и новые инструкции конвейер[Новые инструкции тоже не каждый такт удается декодировать, так что возможна и обратная ситуация: новых инструкций за такт не появилось, и диспетчер отправляет инструкции на выполнение «из старых запасов»]. Так в процессоре возникает разделение на две независимо работающие подсистемы: Front-end (блоки, занимающиеся декодированием инструкций и их подготовкой к исполнению) и Back-end (блоки, собственно исполняющие инструкции).