Журнал «Компьютерра» №37 - Журнал Компьютерра (бесплатные онлайн книги читаем полные версии .txt) 📗
Еще в 1999 году MIPS Technologies упростила свою лицензионную политику, предложив всем желающим два варианта MIPS-архитектуры: MIPS32 - для 32-разрядных систем и MIPS64 - для 64-разрядных. С тех пор эту технологию лицензировали NEC, Toshiba, Broadcom, Philips, LSI Logic и IDT, выпустившие огромное количество специализированных интегрированных процессоров на ее основе. Сегодня MIPS - самая популярная высокопроизводительная[То есть там, где производительность критична, используется MIPS, а где нет - ARM] архитектура, использующаяся во встраиваемых системах. А это львиная доля сетевых устройств (от роутеров Cisco Systems до небольших мостов домашних и офисных сетей); большая часть процессоров игровых приставок прошлых поколений; процессоры для WiFi и VoIP; кодеры-декодеры MPEG; некоторая часть процессоров терминалов, КПК и сотовых телефонов. Не очень завидная участь для бывшего лидера, но если сравнивать с судьбой SPARC или Alpha - не такая уж и плохая.
Шаг 4. Суперскалярные и Out-of-Order-процессоры
У полноценной конвейеризации, более или менее эффективно обходящей перечисленные выше проблемы, есть одно несомненное достоинство: она настолько сложна, что, единожды реализованная, позволяет легко построить на ее основе целый ряд интересных новшеств. Для начала заметим, что коль уж у нас есть очереди готовых к исполнению инструкций и мы знаем взаимозависимости между ними по данным, есть техника переименования регистров, позволяющая разным инструкциям одновременно задействовать одни и те же регистры для разных целей, и, наконец, есть надежно работающая система сброса конвейера, то мы можем:
Запускать на исполняющие устройства сразу несколько инструкций (если они не зависят друг от друга и могут быть безболезненно выполнены одновременно).
Переупорядочивать независящие друг от друга инструкции так, как сочтем нужным.
Процессоры, использующие первую технику, называются суперскалярными. К примеру, сугубо теоретически, по числу исполнительных устройств, Pentium 4 может выполнять семь инструкций за такт, а Athlon 64 - девять. Реальные цифры, конечно, гораздо скромнее и определяются трудностью полноценной загрузки всех исполнительных устройств, однако Pentium 4 все же способен исполнять в устоявшемся режиме две (при некоторых условиях - четыре), а Athlon 64 - три инструкции за такт, одновременно производя две (A64 - три) операции по адресации и выборке данных из оперативной памяти. Может показаться, что реализация суперскалярного процессора очень проста (достаточно со стадии schedule просто распределять инструкции по разным исполнительным устройствам), однако такой лобовой подход обычно упирается в то, что Front-end процессора перестает успевать загружать исполнительные блоки работой. Поэтому на практике хорошо сделанные суперскалярные архитектуры, подобные AMD K7/K8, приходится специально «затачивать» под суперскалярность.
Процессоры, использующие вторую технику, называются процессорами с внеочередным исполнением инструкций (Out-of-Order processors, OoO). Техника переупорядочивания инструкций замечательна тем, что резко ослабляет негативные эффекты от медленной оперативной памяти и от наличия зависимых цепочек инструкций. Если, например, инструкция A обратилась к оперативной памяти, а нужных данных в кэше не оказалось или если A занимается ожиданием результатов выполнения какой-то другой инструкции, то OoO-процессор сможет пропустить вперед другие инструкции, не зависящие от результатов выполнения инструкции A. Кроме того, продвинутый планировщик OoO-процессора иногда может использоваться для реализации специфических деталей той или иной архитектуры - например, для спекулятивного исполнения по данным в случае Pentium 4 или одновременного исполнения нескольких веток программного кода в IA-64. Реализация OoO-процессоров не требует специальной оптимизации всего конвейера - это всего лишь усложнение схемы планировщиков, запускающих готовые к исполнению инструкции на исполнительные устройства в другом порядке, нежели они на планировщики поступили, плюс усложнение схем сброса конвейера и сохранения полученных результатов: результат выполнения прошедших вне очереди инструкций все равно должен сохраняться в последовательности, строго соответствующей расположению инструкций в изначальном коде[Это связано с тем, что если случится какая-то ошибка, то результаты выполнения запущенных вперед очереди инструкции придется аннулировать].
На сегодняшний день не существует ни одного суперскалярного или OoO CISC-процессора. Дело в том, что поскольку для нормальной реализации навороченных диспетчеров и планировщиков все равно требуется длительная и тщательная подготовка инструкций, причем желательно - до такого простого состояния, чтобы эти функционирующие на огромных частотах модули особенно не «задумывались» над тем, что такая хитрая последовательность байтов означает и куда ее следует направить (проблем у них и без того хватает), то любой исходный машинный код Front-end процессоров превращает перед исполнением в некое внутреннее, упрощенное и «разжеванное», состояние. То есть на этом этапе развития различия между RISC- и CISC-архитектурами почти стираются - просто у RISC’ов декодер, превращающий исходный машинный код в содержимое очередей планировщиков, устроен гораздо проще, чем «расковыривающий» хитро упакованные x86-инструкции CISC-подобный декодер AMD Athlon и Intel Pentium. Так что можно сказать, что фактически все современные x86-процессоры «в глубине души» являются полноценными RISC’ами - ведь исходный x86-код они в любом случае преобразуют на лету во внутреннее RISC-подобное представление. Правда, разной сложностью декодеров дело не ограничивается: все-таки классический RISC-код не только проще преобразовывать, но и результирующий внутренний код из него получается лучше - планировщикам гораздо легче его обрабатывать (в нем меньше зависимостей и операций с оперативной памятью). Вот и появляются в x86 все новые и новые расширенные наборы инструкций (от 3Dnow! до SSE): это всего-навсего «внешняя ширма», упрощающая работу декодерам инструкций и позволяющая им сгенерировать более эффективный внутренний код. Специального блока обработки того же упакованного 128-битного формата SSE нет ни в одном современном процессоре, так что когда в программном коде x86 встречается, скажем, инструкция сложения двух регистров SSE по четыре числа в каждом - декодер банально генерирует код из четырех явно независимых (вот за что боролись!) инструкций сложения, которые планировщику потом будет легко разбросать по исполнительным устройствам. Но какого-либо «специального блока SSE», одновременно выполняющего запрошенные одной инструкцией четыре сложения, ни в Athlon, ни в Pentium 4 нет.
Фактически развитие собственно «архитектуры» x86-процессоров долгое время стояло на месте: что древний Pentium Pro, что новейший Pentium M - все они основаны на одной и той же старой-престарой архитектуре P6. Вылизанной, оптимизированной, но старой - ибо повода для ее смены до сих пор просто не было; «внутреннее представление» x86-кода, несмотря на все внесенные в x86 новации, с тех самых древних времен «чистой IA-32» вплоть до появления технологии AMD64 практически не изменялось.
К сожалению, нет места для рассказа об архитектурах VLIW и Cell - потенциальных претендентов на замену суперскалярных OoO-процессоров, так что о них мы поговорим в следующий раз. А пока рассмотрим самые популярные примеры «классических» подходов - в их видении Intel и AMD.
Блок предсказания переходов
Да-да, именно так называется этот странный блок! Но «гадание на кофейной гуще» здесь ни при чем - переходы предсказываются на основе вполне научных соображений. Обычно используется очень простой способ: в процессоре ведется табличка ранее совершенных переходов - для каждого условного перехода подсчитывается, сколько раз он «сработал», а сколько - «был проигнорирован». Поэтому, скажем, когда процессор встречает переход, замыкающий какой-нибудь цикл, то он быстренько начинает считать: раз переход сработал, два сработал, три сработал - ну, значит, наверное, он всегда будет срабатывать, вот так и будем предсказывать, что переход всегда происходит. То, что мы один раз в конце цикла ошибемся, - не беда, зато ценой максимум двух ошибок мы добьемся точного предсказания во всех остальных случаях. Кстати, на простых циклах процессор, как правило, ошибается еще реже - не более одного раза: по умолчанию, когда не из чего выбирать, считается, что условный переход всегда происходит.