Три архитектуры эльфам, семь гномам, девять людям… где же искать ту, что объединит их все?

  1. Исторический экскурс

    1. Войны CISC vs. RISC

    2. Superscalar vs. VLIW

      1. Основные суперскалярные идеи

      2. VLIW

      3. EPIC

  2. Альтернативные идеи

    1. EDGE

    2. STRANDs

    3. NArch+

  3. А что если

    1. Разметка кода

    2. Без компромиссов

Стремление сделать компьютер «предельных характеристик» существует столько же, сколько существуют и сами компьютеры. Но если поначалу предельность определялась общей надёжностью системы (грубо — числом дискретных элементов,   паяных и разъёмных соединений), то на данный момент развитие упёрлось в такие фундаментальные ограничения, как скорость света и размер атомов. В самом деле, расстояние Si — Si равно 2,34 Å, так что в 7 нанометров можно уложить лишь 30 атомов кремния. И этот кремний еще надо более-менее равномерно легировать.

Технология изготовления микросхем в её нынешнем (планарном) виде доведена до совершенства и существенного прогресса здесь ждать не стоит. Но можно побороться за предельные характеристики архитектуры, за то, что Б.А. Бабаян называет «не-улучшаемой архитектурой». Т.е. той, для которой пара компилятор/процессор способна выдавать на большинстве практических задач близкую к пиковой производительность.

Исторический экскурс

Начнём издалека, стоит осветить технологический ландшафт эпохи, без этого сложно понять логику событий, да и просто интересно.

Первые доступные микросхемы появились в начале-середине 60-х и вызвали революцию в разработке компьютеров. Если советские Эльбрусы, «антисоветские» Cray-1, Cyber-205, … были порождены наличием интегральных схем, то к моменту их постановки в серию уже возникли СБИС (VLSI) которые позволили разместить процессор на небольшом к-ве микросхем. Что дешевле, меньше расстояния, можно поднять частоту, меньше ест, надёжнее… 

Впрочем, эволюция суперкомпьютеров в тот момент упёрлась в технологический предел — процессор, собранный на микросхемах был физически достаточно велик для того, чтобы безнаказанно поднимать тактовую частоту. А технология изготовления микросхем не была достаточно развита, чтобы разместить процессор на одном кристалле. К примеру, процессор Cray-3 состоял из сотен микросхем на арсениде галлия, которые удалось упаковать в объем, равный кубическому футу. 

Фиг.1 Процессорный модуль Cray-3Фиг.1 Процессорный модуль Cray-3

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

На другом конце спектра производительности тоже происходили интересные события. В 60-е годы появилась целая плеяда  мини-компьютеров. Навскидку:

  • HP-2100 появился в продаже в 1966, выпущены десятки тысяч экземпляров, продукты этой серии сделали HP крупнейшим производителем миникомпьютеров и выпускались вплоть до 1990 г. Доступные языки программирования — диалекты FORTRAN-а, ALGOL, BASIC. И ассемблер, конечно, куда же без него.

  • DDP-516 — также 1966. Его вариант Honeywell H316 известен как «кухонный компьютер», в его память был зашит список кулинарных рецептов, а в корпус встроена разделочная доска. Между прочим, именно H316 использовался  в качестве основного компьютера для мониторинга температуры реактора АЭС Bradwell вплоть до 2000 года.Доступные языки — BASIC, FORTRAN, FORTH.

  • МИР — 1965, для инженерных расчетов, основной язык — АЛМИР-65

  • IBM-7094/7074 — 1962/1963 развитие IBM-7090 (первый транзисторный мэйнфрейм). Языки программирования — FORTRAN, COBOL

  • Наири 1964

  • Сетунь 1962

  • Минск 1963

  • PDP-¼/…/10 1960…1969 Языки программирования FORTRAN, FOCAL, DIBOL, BASIC

  • PDP-11 (1970) стоит особо, он 16-разрядный и в нём появился аппаратный стек

Кроме миниатюризации шло и удешевление техники, если IBM-7090 стоил миллионы (тех ещё) долларов, то «кухонный компьютер» H316 уже около 10 000. В результате взрывным образом выросла доступность компьютеров, если число мэйнфреймов исчислялось сотнями, то мини-компьютеры продавались десятками тысяч.

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

Фиг.2 Отладка на R1Фиг.2 Отладка на R1

В то же время на более мощных компьютерах операционные системы достигли определённой зрелости. Стоит упомянуть совместный проект Bell Labs, GE и MIT под название Multics. Multics была задумана как операционная система, реализующая целую кучу революционных идей

  • вытесняющая многозадачность

  • официальное разделение памяти процессов на внутреннюю (оперативную) и внешнюю (файлы)

  • централизованная файловая система

  • сегментно — страничная виртуальная память

  • динамическое связывание — разделяемые библиотеки

  • многопроцессорность

  • встроенная система безопасности

  • конфигурация ОС на-лету

  • в основном написана на языке высокого уровня (PL/1)

В общем, всё то, чего мы сейчас интуитивно ожидаем от операционной системы, впервые в совокупности появилось именно в Multics.

К сожалению, данный проект, как и многие до и после него, был погребён под собственной сложностью. Произошло это в 1969 году и на остатках Multics, благодаря поначалу в основном  Кену Томпсон и Деннису Ритчи из Bell Labs, появились ОС UNIX и язык программирования С.

Фиг.3 Происхождение СФиг.3 Происхождение С 

Невозможно удержаться еще от того, чтобы привести еще одну фотографию.

Фиг.4 Кен Томпсон (сидит) и Деннис Ритчи мучают PDP-11 в Нью-ДжерсиФиг.4 Кен Томпсон (сидит) и Деннис Ритчи мучают PDP-11 в Нью-Джерси

Тем временем шел и невероятно быстрый прогресс в элементной базе. Первыми доступными однокристальными микропроцессорами стали 4-разрядные Intel 4004 (1971) и TMS1000 (1972) от TI. Первым 8-разрядным микропроцессором стал Intel 8008 (1972), а 16-разрядными, по видимому, HP BPC (1975) и TMS9900 от TI (1976). К первым 32-разрядным микропроцессорам можно отнести MC68000 от Motorola (1979), который, впрочем, имел 16-разрядную шину данных и АЛУ. И, наконец, первым 64-разрядным микропроцессором стал MIPS R4000 (1991). Intel i860 появился в 1989 г, но имел 32-разрядное АЛУ и 64-разрядный вычислитель с плавающей точкой (который, впрочем, умел работать с 64-разрядными целыми числами). i860 не был всё же процессором общего назначения, поэтому первенство по праву забирает именно R4000.

Итого, к середине-концу 80-х была доступна целая линейка честных 32-разрядных однокристальных микропроцессоров — Motorola MC68020 (1982), DEC-J11 (1983), Intel i386 (1985), NS32032 (1985), MIPS R2000 (1986), Fairchild Clipper (1986), SPARC V7 (1987), MIPS R3000 (1988), Motorola 88K (1988), AMD29K (1988), Intel i960 (1989).

Далее подтянулись MIPS R4000 64 (1991), HP PA-RISC 64 (1992), SPARC-V8 32(1992), PowerPC 32/64 (1993), DEC Alpha AXP 64 (1993), SPARC-V9 64 (1995)…

Войны CISC vs. RISC

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

В то же самое время компьютеры среднего класса стремительно дешевели, так, Interdata представила в 1973 г. первый 32-разрядный мини-компьютер (IBM 360 совместимый) дешевле 10 000 долларов. В какой-то момент эти два процесса должны были столкнуться. 

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

  • Часть разработчиков видела возможность сделать всё тоже самое, но компактнее, дешевле и мощнее. Например, DEC выпустила однокристальные процессоры T11(PDP-11) / J11(VAX). Или Texas Instruments c TMS9900 — однокристальным процессором, совместимым с популярной серией миникомпьютеров TI-990. Упомянем еще NS32K как развитие VAX. Глядя из будущего, легко понять, почему этот путь оказался тупиковым.

  • Другая часть проектов росла снизу вверх и имела успешные эволюционирующие продукты. К таким отнесём x86 и MC68K.

    У Intel была простая и успешная серия 8086 наряду со сложной и провальной 432 серией. Может поэтому, архитектура развивалась достаточно эволюционно. 8086 превратился в дешевый 8088. Следующий шаг — 16-разрядные 80186 и более простой и дешевый 80188. 80286 16 разрядный процессор с 24-разрядной адресной шиной. 32-разрядный 80386 процессор с MMU тоже был совместим с изначальным 8086. При этом нельзя сказать, что Intel боялась экспериментировать — у них был и многопроцессорный iPCS с топологией гиперкуб и i860 с элементами VLIW и i960 с регистровыми окнами. Но об этом позже.

    Аналогично поступила Motorola с удачной серией 68K, которая развивалась эволюционно — наращивали частоту, сделали полноценную 32-разрядную шину данных, АЛУ, … Причем, до этого существовала относительно удачная 8-разрядная серия 6800, которую они бросили и не побоялись сделать всё с нуля.

  • С третьей группой интереснее всего. Здесь отметились люди из академической среды, равно как и разработчики/исследователи из индустрии, которые увидели редкий шанс, имея за плечами и знания и практический опыт разработки процессоров, сделать новую[ые] архитектуру[ы] с нуля, на новом техническом уровне, по возможности избавившись от груза накопленных ошибок.

Необходимо учитывать также следующие обстоятельства.
Во-первых. На тот момент уже существовала свободно распространяемая (~ по цене магнитной ленты) переносимая операционная система (UNIX) с переносимым системным языком С. Причем, они еще не успели обрасти мышцами и накопить жирка, так что добавление новой архитектуры или учет её изменений не были слишком дорогостоящими. В начале UNIX развивался преимущественно энтузиастами и в 1985 году уже существовал в версии V8/BSD4.2 и был портирован с PDP-11 на VAX и Interdata 8/32 (IBM 360),
а также имелись коммерческие
386/ix от Interactive Systems Corp,
Xenix от Microsoft (x86, PDP 11, Z80, MC68K),
Idris от Whitesmiths (PDP 11, VAX, MC68K, IBM 370, Atari).

Во-вторых, произошла тихая революция в компиляторах. Условной точкой отсчета можно считать 1981 год, когда математиком Грегори Хайтином (Gregory Chaitin) был предложен [4,5] способ  распределения регистров.

Речь идет о той стадии компиляции [6], когда программа преобразована во внутреннее представление в виде трёхадресного кода и до стадии кодогенерации. Трёхадресный код — фактически код для процессора с бесконечным количеством регистров. Но в конечной архитектуре количество регистров ограничено и требуется решить, какие значениях можно оставить в регистрах, а какие следует переместить в память (грубо).

До того момента эта стадия компиляции выполнялась эмпирически, с помощью набора разнообразных рецептов/приёмов, которые следовало применять в тех или иных ситуациях, качество такого кода зачастую оказывалось сомнительным. Грегори Хайтин пришел к выводу, что задача распределения регистров сводится к задаче раскраски графа. Про эту задачу известно, что она NP-полная, т.е. стоимость ее решения экспоненциально зависит  от числа вершин (исходных регистров, которые надо распределить). К счастью, в данном случае это не приговор, поскольку была также предложена приемлемая эвристика. В результате удалось создать компилятор (для языка PL/1), причем качество генерируемого им кода было сопоставимо с написанным вручную на ассемблере.

Грегори Хайтин участвовал в проекте IBM 801 который не получил широкого распространения, но опыт и знания, полученные в нём были использованы в дальнейшем в архитектуре POWER. Проект начат в 1975 г. как инициативная разработка. Была собрана и проанализирована статистика по огромному количеству реальных программ (частоты использования инструкций, тайминги, работа с памятью …) и сформулированы требования к перспективной архитектуре.

Надо сказать, что в то время идея перспективной архитектуры просто висела в воздухе и занимались ею не только в IBM, стоит упомянуть Berkeley RISC и Stanford MIPS проекты. Консенсус, сложившийся на тот момент был таков:

  • Микрокод — зло. Компьютеры с традиционной, т.н. CISC архитектурой традиционно имели набор инструкций, отличный от внутреннего представления этого кода. Причин несколько. Разработчики стремились к как можно более компактному коду, ведь память была не просто дорогой, её было совсем немного и хотелось поместить в неё как можно больше кода. Кроме того, если разработчики при анализе реально работающего кода (написанного преимущественно вручную на ассемблере) видели постоянно повторяющиеся паттерны инструкций, они стремились в следующей итерации архитектуры внести такие паттерны в архитектуру в виде новых инструкций [7].

    В результате возник такой механизм как микрокод, который преобразовывал внешнее представление во внутреннее, пригодное для исполнения.

    Эндрю Танненбаум пишет  [3], что опкоды CISC инструкций напоминают ему коды Хаффмана, применяющиеся при сжатии данных. Пожалуй, это не удивительно. Микрокод можно/стоит воспринимать как аппаратный распаковщик для минимизации входного потока кода.

    Объем логики, требовавшейся для работы микрокода, был не так уж и мал. Например, в MC68000 он занимал до 20% от площади микросхемы, т.е. его обслуживало ~ 15 000 из 68 000 транзисторов (да, процессоры серии 68000 состояли из чуть более чем 68000 транзисторов).

    Фиг.5 Микрофотография MC68000, к микрокоду относятся области NROM & uROMФиг.5 Микрофотография MC68000, к микрокоду относятся области NROM & uROM

    Идея, которую заложили в новый класс архитектур (RISC) заключалась в том, чтобы выровнять внутреннее и внешнее представление кода, избавиться от микрокода, а высвободившиеся транзисторы отдать под что-нибудь полезное, например, сделать побольше регистров.

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

    Глядя на это из современности, отказ от микрокода видится затеей сомнительной и конъюнктурной. Да, микрокод 68000 занимал 20%, но в транзисторах это величина примерно постоянная, а общий размер процессора нет. Для 68020 с его 190 000 транзисторов это уже 8%, а для 68040 — ~1%.

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

  • Нужно больше регистров. С этим не поспоришь. Регистры являются сверхоперативной памятью — значения в них уже готовы для загрузки в исполнительные устройства процессора. Иногда их называют кэшем уровня L0.

    В CISC архитектурах число регистров общего назначения невелико

    • 68K — 8 

    • x86 — 8

    • PDP-11 — 8

    • IBM 360 — 16

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

    Этим и вызваны работы Грегори Хайтина по автоматическому распределению регистров. В IBM-801 поначалу было 16 штук 24-разрядных регистров, но примерно в 1980 г. архитектуру перепроектировали под 32 32-разрядных регистра. Распределять руками 32 регистра не слишком эффективно, обычный компилятор PL\1 так же не мог эффективно справляться с ними в силу того, что был оптимизирован под 16 регистров IBM-360/370.

    Концепция кэш-памяти в те годы была не в новинку и впервые появилась в IBM 360/86 (1968). В производительных моделях мини компьютеров она также присутствовала, но в первых однокристальных микропроцессорах её не было. Когда кэш память появилась в CISC процессорах, она в какой-то мере компенсировала малое количество регистров. Попробуем оценить эту компенсацию.

    Сравнительные тесты экспериментального RISC II и 68000 показали, что первый в 1.4…4.2 раза быстрее. При этом 68000 имеет 16 регистров общего назначения, а RISC II — 22 регистра в окне вызова процедуры плюс 10 глобальных (всего 138 регистров). Известно, что 68000 не имеет кэш памяти, она в линейке появилась в процессоре 68020, это были 256 байт кэша инструкций. Кэш данных добавили в 68030, тоже 256 байт. Сравним их относительную производительность.

    Известно, что 68000 выполнял в среднем 0.175 инструкций за такт. Для 68030 эта величина — 0.36 инструкций за такт. Следовательно, если привести RISC II к 68030, ускорение первого составит 0.68…2.04 раза. Что ж, результаты становятся не столь однозначными.

    Интересно, что VAX 11/780, который выполняет 0.2 инструкции за такт, но содержит 8K кэш, в 0.85…2.65 раза медленнее RISC II.

  • Конвейер — благо. Каждый знает что такое конвейер. Идея известна с 60-х годов и к началу 80-х использование по крайней мере двухстадийного (пока одна инструкция исполняется, другая декодируется) конвейера было общепринятой практикой. Во всяком случае в 68000 был именно такой двухстадийный конвейер.

    Фиг.6 типичный RISC конвейерФиг.6 типичный RISC конвейер

    Обозначения на Фиг.6
    IF — instruction fetch
    ID — instruction decode
    EX — execute
    MEM — memory access
    WB — write back, сохранение результата

    Упрощенные инструкции RISC на первый взгляд более подходят для конвейерной работы. Исполнительные устройства в RISC работают только с регистрами, если нужны данные из памяти, их предварительно требуется загрузить. Т.е. более сложные CISC инструкции после микро-кодирования распадаются на несколько внутренних (микро)инструкций, тогда как стадии конвейера касаются их всех, что менее эффективно.

    В действительности, особой разницы нет, грубо, в CISC к конвейеру добавляется еще одна стадия — микро-декодирования. На производительности это явным образом не сказывается.

    Для примера, в 68020 (1982 г.) появился трёхстадийный конвейер, в 68040 (1990 г) стадий стало 6 (и, кстати, кэш по 4 килобайта для данных и кода) с соответствующим приростом производительности — 1.1 (суперскаляр или ошибка ?) инструкции на такт.

    Для сравнения, MIPS R3000 (1988 г.) имел 5-стадийный конвейер, не содержал внутри-процессорного кэша и имел производительность близкую к 1 инструкции за такт.

  • Проще инструкции — проще процессор — больше частота.
    Сравним одноклассников

    CISC

    • 68030 — появился в 1987 г., 273 000 транзисторов, 3-стадийный конвейер, производительность 0.36 инструкций на такт, частоты 16…50 МГц, кэш внутренний 256 байт + 256 байт, техпроцесс .8 мкм

    • 386DX —  появился в 1985 г., 275 000 транзисторов, производительность 0.13 инструкций на такт, 16…40 Мгц, кэш внешний, техпроцесс 1.5 мкм

      RISC

    • SPARC MB86900 (V7) — появился в 1986 г., 110 000 транзисторов, 5-стадийный конвейер, производительность 0.51 инструкций за такт, частота 14.3…33 МГц, кэш внешний, техпроцесс 1.2 мкм

    • R3000 — появился в 1988 г., 110 000 транзисторов, 5-стадийный конвейер, производительность 0.7…0.86 инструкций за такт, частоты 20…33 МГц, кэш внешний, техпроцесс 1.3 мкм

    Производительность этих микропроцессоров сопоставима, особенно учитывая, что CISC инструкции «тяжелее» — выполняют больше работы. Насчет простоты — да, это правда, при сравнимой производительности RISC содержат на кристалле в 2.5 раза меньше логических элементов.

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

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

Так или иначе, началась технологическая гонка, которую мы знаем как «CISC/RISC войны». Основное технологическое преимущество RISC — регистры было нивелировано внутри-кристальным кэшем, обе стороны наращивали его объем, тактовую частоту и сложность конвейеров, интегрировали сопроцессоры с плавающей точкой … В какой-то момент микропроцессоры научились переименовывать регистры, переставлять микро-инструкции… и тут на сцену вышли суперскалярные микропроцессоры, которым стало (по большому счету) всё равно, какого типа — RISC или CISC у них набор внешних инструкций. С этого момента понятие RISC — не более, чем название раскрученного брэнда, которое используется преимущественно по инерции (либо маркетологами).

Главными проигравшими в этой борьбе стали те технологические компании, что не смогли или не успели включиться в соревнование и постепенно потеряли свою аудиторию. Главными выигравшими — те, кто смог выдержать 10-летнюю гонку. И, конечно, пользователи, которые с осторожным оптимизмом оплатили всё это великолепие.

Superscalar vs. VLIW

К началу 90-х годов сложилась довольно интересная ситуация. Коммерчески доступными на тот момент были следующие 32-разрядные микропроцессоры:

CISC

  • Motorola 68040 — 25 и 33 Мгц, производительность 0.7…1.1 (целочисленных) инструкций на такт, 1.17 млн транзисторов

  • Intel 486DX — 25 и 33 Мгц, производительность 0.338 инструкций (0.388 DX2, 0.7 DX4) на такт, 1.18 млн транзисторов

RISC

  • SPARC V8 — 33…40 МГц, , производительность 0.6 инструкций на такт, 1 млн транзисторов

  • MIPS R3400, R3500 — 25…40 МГц, 0.7 инструкций на такт

  • IBM POWER, 25 МГц, 0.75 инструкций на такт

  • HP PA-7000, 66 МГц, 0.58 млн транзисторов,   0.66 инструкций на такт

  • Intel i960 — 16…33 МГц, по-видимому первый микропроцессор с суперскалярным ядром, но без сопроцессора и MMU

  • AMD 29K — 16…40 МГц

  • Motorola 88100 — 25…33 МГц

К 1990–1991 гг среди 32-разрядных микропроцессоров существовал довольно широкий выбор, причем производительность отличалась не на порядки. Немного особняком стоит i960, который предназначался для встраиваемых систем, впрочем, был  вариант и с MMU и с сопроцессором. ARM не упоминается т.к. на тот момент находился в зачаточном состоянии.

Вариантов в каком направлении развиваться было несколько

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

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

Фиг.7 Зависимость числа транзисторов  микропроцессора от времениФиг.7 Зависимость числа транзисторов  микропроцессора от времени

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

Фиг.8 Микрофотография 80486DX2Фиг.8 Микрофотография 80486DX2

На Фиг.8 показана микрофотография 80486DX2, из общих 1.2 млн транзисторов почти половина приходится на кэш (под номером 6).

Отдельно стоит остановиться на вопросе, почему нельзя бесконтрольно увеличивать число регистров в архитектуре.

Поскольку одна из основных идей RISC процессоров это большое по сравнению с CISC число регистров, почему бы еще не увеличить  их количество при выпуске новой версии архитектуры (как это в дальнейшем произошло, например, при переходе x86 => x86–64)?

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

Когда регистров мало, как в PDP-11, то и проблемы никакой нет, значения параметров вызова передаются через стек (cdecl). Когда их становится больше, появляется соблазн передать параметры вызова через регистры (что регламентируется ABI архитектуры).

  • Регистровые машины (CISC + наследники Stanford MIPS: MIPS, PA-RISC, PowerPC, 88K). Регистры делятся на две категории — volatile: ответственность за сохранение их содержимого лежит на вызывающей стороне и non-volatile — те, о ком должна заботиться вызываемая функция.Вот, например, как обстоят дела с с 32-разрядным компилятором OS X для PowerPC, где регистров по 32 — целочисленных и с плавающей точкой:

    • volatile: GPR0, GPR2: GPR10, GPR12, FPR0: FPR13, всего 11 + 14

    • non-volatile: GPR1, GPR11(*), GPR13: GPR31, FPR14: FPR31, всего 21 + 18 GPR11(*) — non-volatile для листовых функций (из которых нет других вызовов)


    Соответственно, чем больше регистров в наличии, тем больше забот с сохранением их содержимого при вызове функций.

    32 регистра общего назначения как раз является компромиссным значением для соблюдения баланса между производительностью вычисления выражений и лёгкостью вызова функций.

  • Процессоры с регистровыми окнами (Berkeley RISC: i960, SPARC, AMD29K). У этого класса выполняемая функция имеет доступ только к регистрам своего т.н. «окна». Окно состоит из трёх частей: in — параметры вызова текущей функции, local — внутренние данные и out — параметры вызова дочерней функции. При вызове окно сдвигается и out часть вызывающей стороны становится in частью вызванной. 

    Сами регистры при этом организованы как кольцевой буфер. По мере роста стека окон, данные выталкиваются в память —  (SPILL). Если стек регистров недо-заполнен, данные подгружаются из памяти (FILL).

    У представителей этого семейства обычно много регистров, например, в AMD29K 64 глобальных и 128 локальных (стековых) регистров. В архитектуре SPARC 8 глобальных регистров и произвольное число локальных, кратное размеру окна (24). Типично 5 окон — всего 128 регистров. Изначально предполагалось, что дешевые процессоры в линейке будут содержать мало окон, а дорогие — много. Собственно, буква S в SPARC означает Scalable.

    В SPARC окно фиксированного размера, в AMD29K — нет, тем не менее, использование окон большого размера при вызове функций будет приводить к тому, что поток данных в/из памяти вырастет и сведёт на нет весь выигрыш от большого количества регистров в контексте функции.

Переход на 64-разрядную архитектуру. Отличный способ найти применение всем тем дополнительным транзисторам, которые прогрессирующая технология позволяет разместить на кристалле. 

Предыдущий переход с 16 разрядов на 32 дал значительный прирост производительности т.к. реальные данные с которыми приходится иметь дело далеко не всегда укладываются в 16 разрядов, эмуляция 32-разрядных операций дорога, кроме того постоянно приходилось следить за потенциальным переполнением.

Переход 32 => 64 не столь очевидно полезен, 32 разрядов вполне достаточно в большинстве случаев, а нагрузка на память возрастает. Пропускная способность памяти между тем — одно из основных узких мест в производительности.

Единственный бесспорный плюс от 64 разрядных архитектур — отсутствие ограничений на размер виртуального адресного пространства, ранее ограниченного 2 Гб (31 разряд + столько же для ОС). Впрочем, для массовых пользователей это преимущество не было очевидно вплоть до конца нулевых годов. 

А вот в области высокопроизводительных вычислительных систем (HPC) преимущества 64 — разрядных архитектур были бесспорны. Так или иначе, все производители микропроцессоров начали осваивать эту нишу. 

  • первым стал MIPS R4000 — появился в 1991 году, 100 МГц ядро, 1.3 млн транзисторов (8К+8К кэш), 2.3 млн для 16К+16К кэша (при 0.11 млн транзисторов для R3000 без кэша)

  • SPARC V9 (UltraSPARC STP 1030) — появился в 1994 г., 143 МГц, 5.2 млн транзисторов, 16К+16К кэш

  • HP PA-RISC 7100 — появился в 1992 г., 80 МГц, 0.8 млн транзисторов при кэше 2К + 1К

  • DEC ALPHA (21064-AA) — появился в 1992 г., 150 МГц, 1.68 млн транзисторов при кэше 8К + 8К. DEC бросила VAX и сделала сразу 64-разрядный RISC.

  • PowerPC (PPC 601) — появился в 1993 г. (полностью 64-разрядный процессор PPC 620 1997 г.), 80 МГц, 2.8 млн транзисторов при кэше 32К.  Сделан альянсом Apple-IBM-Motorola на основе архитектур POWER и 68040. Motorola ради этой архитектуры бросила свои ветки 68K и 88K.

И с большим опозданием:

  • Itanium от Intel — появился в 2001 г., это VLIW процессор, об этом позже. Совместный проект Intel и HP.

  • AMD64 от AMD (Opteron) — появился в 2003 г., Intel (Xeon Nocona) — 2004 г., это развитие x86. Intel стала развивать эту ветку после того, как стало понятно что Itanium не пользуется успехом.

И, наконец, архитектурные изыски, использование параллелизма. Практически любая программа обладает некоторым параллелизмом. Два выражения, которые не зависят друг от друга по данным, могут быть вычислены параллельно. 

Мы не будем рассматривать программный параллелизм, равно как и использование векторных вычислений. Первое просто вне темы данной статьи, а второе до сих пор относится к области магии. Векторные вычисления действительно могут заметно ускорить вычисления, но это требует ручной работы, вставке подсказок компилятору… Современные компиляторы пользуются для векторизации набором рецептов на разные случаи жизни подобно тому, как это происходило до того как был разработан алгоритм более-менее оптимального распределения регистров (работы Грегори Хайтина). А нас больше интересует насколько пара компилятор/процессор способна справиться с параллелизмом в обычной программе.

Если ранее мы рассматривали в основном процессоры, способные выполнять одну инструкцию за такт (скалярные), то теперь нас интересуют суперскалярные, т.е. выполняющие несколько инструкций параллельно.

Основные суперскалярные идеи

Мы здесь не будем рассматривать «поползновения вширь» в виде векторных вычислений и/или многопроцессорные (многоядерные) вычисления.

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

Работа в многопроцессорном режиме (SMP, NUMA, когерентность памяти …) это совершенно другая тема для разговора, очень важная, но другая. Здесь же разберём ситуацию — один процесс, один поток, один процессор (ядро).

  • несколько специализированных конвейеров вместо одного общего. RISC конвейер даже в идеальном случае не способен выполнять больше одной инструкции за такт. Кроме того, не все операции могут быть выполнены за один такт, например, умножение или деление. В результате, во время исполнения длинной операции остальные стадии конвейера простаивают. Поэтому возникла мысль разбить общий конвейер на несколько меньших по функциональному предназначению. Рассмотрим на примере Motorola 88K (1988 г.)

    Фиг.9 Функциональная схема 88100Фиг.9 Функциональная схема 88100

    Итак, процессор содержит пять разных конвейеров:
    1) fetch/decode, который распределяет инструкции по остальным конвейерам
    2) доступ к памяти
    3) АЛУ (однотактный)
    4,5) сопроцессор с плавающей точкой (+ целочисленные умножения и деления).
    Т.е. теоретически 88100 мог выполнять до 5 инструкций одновременно.

    Такую структуру можно рассматривать как единый обобщенный конвейер, без линейного прохождения всех стадий. Это, скорее,   машина состояний, где порядок прохождения стадий зависит от самой инструкции. В принципе, таковым является и обычный RISC конвейер (Фиг.6), в котором присутствуют стадии вычисления (EX) и доступа к памяти (MEM). RISC инструкция может или обращаться к памяти или выполняться, нет смысла тратить лишний такт на несуществующий этап.

  • несколько основных конвейеров

    Фиг.10 Два независимых конвейера.Те же стадии что и на Фиг.6Фиг.10 Два независимых конвейера.Те же стадии что и на Фиг.6

    Типичный пример — Pentium [9] (1993), у которого.

    • параллельно работающие конвейеры U и V (аналогичные оному из 486).

    • некоторые инструкции являются сочетаемыми (pairable)  для U&V: MOV регистр, память или целое (immediate) в регистр или память, PUSH регистр или целое, POP регистр, LEA, NOP, INC, DEC, ADD, SUB, CMP, AND, OR, XOR, некоторые виды TEST

    • некоторые инструкции являются сочетаемыми для U: ADC, SBB, SHR, SAR, SHL, SAL с целым, ROR, ROL, RCR, RCL на 1

    • некоторые инструкции являются сочетаемыми для V: близкие (near) переходы, включая условные и вызов близких функций

    • две последовательные инструкции выполняются параллельно, если одна из них сочетается с U, вторая с V и вторая инструкция не работает с регистром, в который пишет первая


    Итого, некоторые целочисленные операции могут выполняться одновременно, что делает Pentium суперскалярным процессором. Тем не менее, такая конструкция не прижилась в том числе потому, что никак не помогает в борьбе с основным узким местом — работе с памятью.

  • внеочередное выполнение инструкций (Out-of-order execution, OoO)
    Исполнение инструкций по готовности требующихся функциональных устройств, а не в порядке следования в коде. Интуитивно понятная идея, которая была реализована довольно рано — еще в CDC 6600 (1964 г.) и IBM System/360 Model 91 (только для вычислений с плавающей точкой, 1967 г.).

  • scoreboard — техника, применённая в CDC 6600 для OoO.

    © Habrahabr.ru