`

СПЕЦИАЛЬНЫЕ
ПАРТНЕРЫ
ПРОЕКТА

Архив номеров

Best CIO

Определение наиболее профессиональных ИТ-управленцев, лидеров и экспертов в своих отраслях

Человек года

Кто внес наибольший вклад в развитие украинского ИТ-рынка.

Продукт года

Награды «Продукт года» еженедельника «Компьютерное обозрение» за наиболее выдающиеся ИТ-товары

 

Процессоры и генерации

+22
голоса

В рамках публикации архивных материалов предлагаем вашему вниманию статью из №28 (247) «Компьютерного Обозрения» от 25 июля 2000 г. В то время на рынок вышла компания Transmeta, которая вызвала большое внимание и споры в отношении классических процессорных архитектур.

Недавняя шумиха вокруг секретного «суперпроекта» Transmeta уже практически сошла на нет. Два ведущих производителя ПК (Compaq и Dell) по сути еще до серийного производства новых процессоров отказались от их использования, в поведении третьего — IBM — наблюдается неафишируемый, но весьма ощутимый скепсис. Точку над «i» в процессе рыночного становления Transmeta поспешила поставить Intel — ответный удар империи оказался на удивление своевременным и мощным.

К слову, фактор выхода такого игрока, как Intel, в новый сегмент рынка (низкопотребляющих производительных CPU), во многом инспирированный именно накалом страстей вокруг Transmeta, по неизвестным причинам не вызвал широкого резонанса в прессе. А напрасно — достойные Копперфилда скорость и ловкость, с которыми полупроводниковый гигант обнародовал 2-ваттную версию Pentium, позволяют догадываться: подобные разработки существовали если не очень, то сравнительно давно. А это означает, что Intel пыталась поддерживать некоторое равновесие, оставляя обширное поле действия игрокам, не предъявляющим рыночных претензий на «настольную часть пирога», Теперь равновесие нарушено, и, в отличие от так и не успевшей проявить себя Transmeta, в неизбежно тяжелом положении оказываются замечательные чипмейкеры (в первую очередь, клонов MIPS — именно они попадают в конкурирующий диапазон производительности и потребляемой мощности). Так что по-настоящему серьезные последствия «e-CPU-бизнеса» в Transmeta-исполнении нам предстоит увидеть в недалеком будущем.

В сюжете этой картины «кисти неизвестного абстракциониста» остался вне внимания еще один важный фрагмент. Недавно опубликованные в Сети результаты тестирования JIT-компиляторов для Java выявили весьма неожиданные, но предсказуемые факты. А именно, Java-код, «перемолоченный» новыми компиляторами... на ряде задач существенно превосходит по быстродействию скомпилированные программы на С и С-м— основных языках системного и прикладного программирования. Близость идей ЛТ идеям, заложенным в процессоры Transmeta, и одновременная технологическая пропасть между ними (ЛТ — быстрая генерация исполняемого кода для «классической» архитектуры целевого процессора из кодов «метамашины», Transmeta — быстрая генерация исполняемого кода метамашины из кодов процессора «классической» архитектуры) дают в руки конкурентов Transmeta еще одно «смертоносное оружие». Его эффективность подтверждается успехами JIT-технологии, а технологическая привлекательность — появлением экономичных и мощных CPU. И, что самое главное, оно обладает неоспоримой потребительской привлекательностью, позволяя существенно ускорить выполнение программ без аппаратной модернизации.

RISC, CISC, VLIW, MISC

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

То, что недавно называлось RISC (компьютер с сокращенным набором команд), трансформировалось в более определенное понятие: «регистрово-насыщенный процессор, ориентированный на операции типа load/store». К сожалению, хоть каких-нибудь числовых оценок, позволяющих утверждать, что тот или иной процессор относится к данному классу, не существует. Но RISC остаются узнаваемыми — регистров у них действительно много (до нескольких сотен), и они действительно «не любят» операций типа «регистр-память» или «память-память»: все операнды, необходимые для выполнения той или иной команды, должны быть обязательно размещены в рабочих регистрах процессора. Эта особенность RISC очень важна — она является и достоинством, и недостатком одновременно. С одной стороны, load/store-архитектура позволяет реализовывать очень быстрые однотактные многооперандные команды (например, классическое в цифровой обработке «умножение с накоплением» — умножение двух операндов и сложение результата с третьим), и это просто замечательно. С другой, управление распределением регистров для разработчиков компиляторов превращается в настоящий кошмар, а необходимость временного сохранения большого числа регистров при вызовах подпрограмм или обработчиков прерываний требует многочисленных архитектурных ухищрений.

Соответственно CISC-семейство в рамках новой классификации остается антиподом RISC и трансформируется в «регистрово-ненасыщенную архитектуру, способную равноценно использовать регистры и оперативную память». В CISC не запрещены команды типа «сложение содержимого регистра с содержимым ячейки оперативной памяти с адресом А» или даже «сложение содержимого ячеек оперативной памяти с адресами А и В и размещение результатов в ячейке ОЗУ с адресом С». Естественно, что такие операции являются в некотором роде абстракциями — для их выполнения процессор осуществляет внутренние операции, что позволяет реализовать «ну очень быструю» систему команд — за один такт с такими задачами не управиться. Зато разработчикам генераторов кода компиляторов становится легче — как ни «насыщай» процессор регистрами, в ОЗУ ячеек все равно больше. Несмотря на это конструкторы процессоров CISC-семейства, как и их коллеги из конкурирующей RISC-отрасли, ухищрениями (подчас лихими) также не брезгуют.

Если с RISC и CISC все более или менее понятно, то недавно прошедшая пик популярности VLIW-архитектура «по молодости лет» остается в некотором роде загадкой. Хотя... совсем недавно автору «в завалах» отчетов о НИР MIT (Массачусетского Технологического) попалось весьма забавное определение VLIW, построенное по принципу «от известного»: VLIW — это «большая MMX-архитектура». Действительно, VLIW фактически подразумевает создание одного «большого» и в некотором роде виртуального процессора из параллельной группы «маленьких». Новизны бы здесь не было никакой, если бы не программный способ этого объединения — за его формирование отвечает генератор кода компилятора. Архитектурные особенности вынуждают разработчиков использовать в качестве «подпроцессоров» RISC-машины: только механизм load/store позволяет «упаковать» в одно длинное слово команды группу подкоманд, выполняющихся подпроцессорами.

Все три предыдущих класса объединяют две принципиальные детали: во-первых, существование отдельного адресного пространства, в котором размещаются регистры процессоров; во-вторых, использование абстракции стека для сугубо служебных целей. Что это значит, лучше всего понять на примере простых 8-битовых RISC и CISC-машин AVR и 8051 производства компании Atmel.

Итак, AVR. Типичный RISC — только load/store команды, достаточно много регистров (32 для 8-битовой машины — это действительно много). Рассмотрим команду ADD Rd,Rr — сложение содержимого двух регистров с размещением результата в одном из них. В двоичном представлении (единственном, понимаемом процессором), она выглядит так:

00011rdddddrrrr

Биты, обозначенные буквами г и d, образуют адреса регистров, участвующих в операции, следовательно, регистровое адресное пространство (РАП) действительно существует и его размер в точности соответствует количеству рабочих регистров (5 битов позволяют адресовать ровно 32 регистра). Стек в AVR используется для временного сохранения/восстанов-ления содержимого рабочих и служебных регистров при переходе к выполнению подпрограммы (или обработчика прерывания).

Классический чип 8051, напротив, является образчиком CISC. Причем наглядно демонстрирующим и ухищрения, необходимые для достижения высоких скоростных качеств. В нем существует аналогичная рассмотренной выше команда ADD A,Rr. Ее битовый формат

00101rrr

свидетельствует, что у 8051 всего лишь 8 регистров, находящихся в РАП. А дальше начинаются упомянутые ухищрения. Стек в 8051 используется так же, как и в AVR, а дополнительным подтверждением CISC-ориентированности является обширный выбор операндов: от регистров в текущем РАП до содержимого ячеек памяти во внутреннем ОЗУ.

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

Последний малочисленный класс архитектур — MISC (компьютер с минимальным набором команд) принципиально отличается и от RISC, и от CISC именно по этим двум критериям. Во-первых, у MISC нет регистрового адресного пространства (а если оно и есть, то недоступно в явном виде программисту), во-вторых, концепция стека играет здесь не второстепенную, а ведущую роль. Это «во-вторых» является неизбежным следствием из «во-первых»: только стек позволяет исключить необходимость в адресах регистров, да и в самих регистрах вообще. Соответственно для MISC самая высокая скорость перехода к выполнению подпрограммы гарантирована — здесь не требуется ни одной «лишней» операции. Кроме того, MISC характеризуются очень компактным кодом, простотой реализации как самих процессоров, так и VLIW-подобных архитектур на их основе. Но к достоинствам MISC мы еще вернемся в конце статьи.

КОМПИЛЯЦИИ, ГЕНЕРАЦИИ, ОПТИМИЗАЦИИ

Увы, даже абсолютно не интересующиеся принципами работы компьютеров и технологией их программирования пользователи становятся в буквальном смысле заложниками этих двух важнейших составляющих компьютинга (если более точно, то именно такие пользователи становятся заложниками в первую очередь). На первый взгляд, какое отношение имеет узкоспециализированная отрасль computer science (проектирование генераторов кода компиляторов) к потребностям рядового пользователя? На самом деле, абсолютно непосредственное: все, что мы привыкли называть качеством ПО (показатели надежности, быстродействие, функциональная насыщенность), определяется прямо или косвенно именно совершенством... генератора кода, используемого разработчиками системных и прикладных программ. Что же, кроме истории с Transmeta, происходит в этой «закрытой зоне»?

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

Статическая генерация кода — самый старый и проверенный прием у разработчиков компиляторов. Неочевидность названия не должна смущать — классические (статические) генераторы кода «срабатывают» один раз и формируют то, что мы называем исполняемым файлом. Несомненное достоинство статической генерации — относительная простота реализации. Естественно, что простота эта определяется показателями качества получаемого машинного кода — примитивный статический генератор может использовать всего несколько регистров конкретного процессора, его написание вполне под силу студенту в рамках курсовой работы, но быстродействие получаемых с его помощью программ будет слишком низким. Использование же всех возможностей конкретной архитектуры — дело слишком непростое, и написание кодогенераторов такого класса является «высшим пилотажем» системного программирования, искусством, базирующимся на самых серьезных разработках в области алгебры, топологии и еще десятка смежных дисциплин. Но даже «са-мый-самый» статический генератор кода принципиально не способен учесть одного «маленького» нюанса — обрабатываемых программой данных, т.е. тех данных, которые используются программой при запуске ее некоторым конкретным пользователем в некотором конкретном случае. Здесь опять же примера не избежать. Предположим, что где-то в теле «программы» встречается такое выражение:

А = В/С ;    // переменная А принимает значение частного переменных В и С

Если на этапе компиляции программы значения В и С неизвестны, а они, например, задаются пользователем в ходе исполнения уже скомпилированной программы, статический генератор кода для некоторого виртуального RISC-процессора может сформировать такой фрагмент кода:

LD R2, &В ; Загрузить в регистр 2 значение из ячейки памяти с адресом В
LD R3, &С ; Загрузить в регистр 2 значение из ячейки памяти с адресом С
DIV R1,R2,R3 ; Поместить в регистр 1 частное содержимого регистров 2 и 3
ST &А, R1 ; Записать в память по адресу А содержимое регистра 1

Предположим, что наш виртуальный процессор выполняет команды LD и ST за два такта (это почти всегда справедливо для хороших RISC на операциях с внешней памятью), а команду DIV — за «много» тактов (например, за 16; команды деления вообще обычно являются «долгоиграющими»). Суммарное время (в терминах тактовой частоты) исполнения этого фрагмента кода составит 22 такта.

А теперь представим, что при некотором запуске программы одна из переменных В или С приняла значение, равное целой степени числа 2 (например, просто 2). Знакомые с двоичной системой счисления и архитектурами процессоров хорошо знают, что умножение на 2N равносильно сдвигу содержимого регистра на N битов и что команды сдвигов присутствуют во всех процессорах, и, наконец, что эти команды очень быстрые. Типичное время выполнения команды сдвига в RISC-процессорах — один такт. В нашем примере этот факт означает, что за общность сгенерированной программы нам пришлось в данном конкретном случае расплатиться 15-ю совершенно бессмысленными тактами. Цифра, казалось бы, совершенно незначительная, но если указанный фрагмент кода исполняется в цикле ну с очень большим числом повторений...

Это один специфический и принципиально непреодолимый недостаток статической кодогенерации. Второй намного серьезнее: если мы не располагаем исходными текстами программ Р, а только ее скомпилированным исполняемым файлом для платформы S, то, естественно, ни на какой другой платформе использовать Р без эмуляционных сложностей невозможно. К слову, решение от Transmeta или никак, или почти никак не «лечит» это хроническое заболевание — ведь фактически Crusoe осуществляет эмуляцию чуждой архитектуры «на том, что есть». И если у процессоров семейства Sparc более 200 рабочих регистров (да еще и хитро организованных), никакой эмуляцией с приемлемой производительностью исполняемые файлы Sparc на 64-регистровом Crusoe не «погоняешь».

Для преодоления этих «болезней» есть совсем другие «лекарства» — прекрасные и вполне работоспособные разработки из области динамической генерации исполняемого кода (ДГИК). Даже в определении ДГИК существенно отличается от привычных нам понятий: «создание исполняемого кода исполняемым процессом» значительно расширяет понятие просто «исполняемого процесса». Фактически это означает, что или исполняемое приложение должно содержать фрагмент компилятора — динамический кодогенератор, или же он должен быть неотъемлемой частью операционной системы (естественно, что с помощью ДГИК можно исполнять приложения на разных аппаратных платформах, но в рамках одной ОС). Чтобы избежать неоправданно сложных и неэффективных «эмуляционных затрат», в большинстве ДГИК в качестве основного представления исполняемых файлов используется машинно-независимый промежуточный формат: в Java-системах с ЛТ-кодогенерацией его роль играет байт-код виртуальной машины Java, в конкурирующей (но, к сожалению, малопопулярной) Juice на основе Oberon — так называемые «плоские бинарные файлы» (slim binaries), в «многоцелевом» компиляторе языка С Icc — «BURS-деревья» (и соответствующие алгоритмы). Показатели качества современных ДГИК-систем весьма высоки: так, кодогенератор Icc затрачивает всего 300–400 команд на формирование одной машинной команды (это выполняется при «загрузке» программы и, для частичных фрагментов кода, — в ходе исполнения), созданный же с его помощью код таких типично ресурсоемких задач, как деление/умножение матриц, оказывается быстрее в 4–10 раз по сравнению со статической версией, транслированной хорошим оптимизирующим компилятором. Результаты применения ДГИК, например в операционных системах, также обнадеживают — ядро экспериментальной ОС Synthesis при исполнении на значительно более медленном процессоре оказалось в 56 (!) раз быстрее в выполнении операций байтового чтения/записи файла по сравнению с SunOS. Создатели графических подсистем не обошли вниманием ДГИК, и здесь канонической считается разработка Роба Пайка bitblt («сердце» оконной подсистемы по ОС Plan9) — подпрограмма, объединяющая два прямоугольных фрагмента растровой видеопамяти на основе битовых операций. При классическом, статическом, подходе для реализации bitblt требуется порядка 1 МВ кода, который получается, кроме того, медленным. Пайк создал ДГИК-вариант bitblt, генерирующий код «самого себя» на основе особенностей исходных растровых фрагментов, что дало возможность повысить быстродействие в худшем случае на порядок.

Одной интересной вариации ДГИК от Hewlett-Packard следует уделить особое внимание. Речь идет о разработке с названием Dynamo. В отличие от «чистых» ДГИК, Dynamo в качестве промежуточного представления использует... непосредственно машинный код целевого компьютера (пока только процессоров РА RISC). Соответственно и целевое назначение Dynamo — оптимизация быстродействия уже скомпилированных программ. Реализация Dynamo является лучшим примером, когда кажущаяся абсурдной идея на деле становится плодотворной: Dynamo — «умный» интерпретатор машинных кодов процессора РА-8000, работающий на про цессоре... РА-8000. Принцип работы такого «интерпретатора-акселератора» относительно несложен. Dynamo «исполняет» в режиме интерпретации фрагмент кода программы до тех пор, пока не встретит команду, вызывающую «ветвление» процесса выполнения, в этот момент интерпретатор переключается в режим оптимизации, формирует оптимальный (обновленный) вариант пройденного фрагмента и записывает его в специально выделенную кэш-об-ласть оперативной памяти. Затем процесс повторяется уже с новым фрагментом. Если в дальнейшем программа обращается к адресу, принадлежащему одному из оптимизированных фрагментов. Dynamo передает на выполнение процессору его код из своей кэш-памяти. Несомненные достоинства такого подхода — принципиальная независимость «ускорителя» от типов ОС и процессора, полная «незаметность» его для пользователя.

Совместное применение технологий «чистой» и оптимизирующей ДГИК позволяет как значительно улучшить показатели быстродействия уже существующего ПО, так и уничтожить межплатформенный барьер при торговле программами в «закрытом» (исполняемом) формате. Если пользователей Windows, на сегодняшний день существующей только на одной платформе (х86), вторая перспектива практически не интересует, то от первой наверняка никто не откажется. Остается попытаться ответить на один вопрос — когда же подобные Dynamo оптимизаторы пробьют себе путь из мира рабочих станций к настольным системам?

РАДИКАЛЬНОЕ РЕШЕНИЕ

ДГИК, несомненно, является очень перспективным направлением. Но, как говорил Жванецкий, «...может, в консерватории что-нибудь надо подправить»? Сама потребность в динамических кодогенераторах-оптимизаторах вызвана, с одной стороны, сегментацией рынка аппаратных платформ, рассчитанных даже для работы с одной ОС (что само по себе неплохо), с другой — принципиальным архитектурным разрывом между современными процессорами и техникой компиляции. Оптимальной для компилируемого кода была, есть и в обозримом будущем останется стековая архитектура, называемая MISC Почему на сегодняшний день серийно выпускаются всего две-три аппаратные реализации стековых машин, какова судьба достаточно интересного чипа PicoJava и как в дальнейшем будет развиваться это забытое всеми ведущими производителями направление — здесь вопросов, увы, больше, чем ответов.


Вы можете подписаться на наш Telegram-канал для получения наиболее интересной информации

+22
голоса

Напечатать Отправить другу

Читайте также

 

Slack подает жалобу на Microsoft и требует антимонопольного расследования от ЕС

 
Реклама

  •  Home  •  Рынок  •  ИТ-директор  •  CloudComputing  •  Hard  •  Soft  •  Сети  •  Безопасность  •  Наука  •  IoT