Выдающееся достижение Холла и Хенша последних лет — метод гребенки оптических частот — позволил «привязать» оптические частоты излучения лазеров порядка 1015 Гц к цезиевому эталону времени, работающему с частотой 9,2 ГГц (разница в пять порядков по частоте создавала огромные технические проблемы). Линейчатый спектр лазерных импульсов похож на частую и острую гребенку, которая покрывает весь видимый спектр (что и дало название методу). Хитрый трюк позволяет получить из гребенки биения радиочастоты и сравнить их с цезиевым эталоном. Благодаря этому, точно вычисляется частота каждого из ее зубьев. Этот элегантный метод уже позволяет выпускать коммерчески доступные эталонные излучатели, умещающиеся на лабораторном столе. Дальнейшее развитие метода и переход к аттосекундным импульсам обещает в ближайшие годы повышение точности еще на три порядка и достижение рентгеновского диапазона энергий.
История учит, что рост точности измерений часто ведет к новым физическим открытиям и технологическим прорывам. Работы Холла и Хенша заложили основу для новой проверки фундаментальных физических законов. Теперь стал возможен немыслимый ранее поиск анизотропии пространства, отличий в свойствах вещества и антивещества, возможных вариаций фундаментальных физических постоянных.
Премия по химии разделена между французским ученым Ивом Шовеном (Yves Chauvin) и американцами Робертом Груббсом (Robert Grubbs) и Ричардом Шроком (Richard Schrock) — «за развитие метода метатезиса в органическом синтезе».
Работы лауреатов нынешнего года внесли ключевой вклад в превращение метатезиса, то есть химических реакций обмена, в одно из важнейших направлений современной органической химии. Эти реакции позволяют легко синтезировать огромное количество новых органических веществ — от лекарств и гербицидов до сверхпрочного пластика.
Органические вещества, составляющие основу всего живого, так разнообразны благодаря уникальным свойствам атомов углерода, которые способны образовывать длинные цепочки и кольца с одинарными и двойными связями, а также соединяться с атомами других элементов. В обменных реакциях две органические молекулы разделяются на части путем разрыва двойной углеродной связи и обмениваются своими частями, образуя новые соединения. Такой обмен становится возможным благодаря помощи молекул-катализаторов, играющих роль посредников.
Первые обменные реакции были случайно найдены и запатентованы еще в пятидесятые годы прошлого века. Но лишь в 1971 году Ив Шовен [4] впервые детально описал их механизм и объяснил, какие соединения могут играть роль катализаторов. Ричард Шрок [5] был первым, кто в 1990 году применил эффективный катализатор на основе тантала для обменных реакций. Два года спустя Роберт Груббс [6] разработал еще более эффективный, стабильный и дешевый рутениевый катализатор, который нашел широчайшее применение в промышленности.
Премию по физиологии и медицине получили австралийцы Барри Маршалл (Barry Marshall) и Робин Уоррен (Robin Warren) за открытие роли микроорганизма Helicobacter pylori [7] в развитии гастрита, язвы желудка и двенадцатиперстной кишки. Помимо распространенности заболевания, поражающего, по последним данным, до десяти и более процентов взрослого населения в развитых странах, впечатляет неожиданность открытия.
С позапрошлого века выдвигались самые разные гипотезы о причинах возникновения болезни — тромбоз или другое поражение сосудов, травмирование нежной слизистой желудка грубой пищей, воспаление, разрушительное действие соляной кислоты… Не было недостатка и в теориях, отдававших первенство психоэмоциональному перенапряжению, особенно — разрушительной роли отрицательных эмоций. Оказалось, однако, что эта болезнь не столько от нервов, сколько от «заразы».
В начале 80-х австралийский патологоанатом Уоррен, изучавший кусочки ткани желудка больных гастритом, обнаружил в них спиралевидные бактерии. Этими данными заинтересовался гастроэнтеролог Маршалл, позднее подтвердивший болезнетворность микроорганизмов по всем канонам самоотверженного научного поиска — выпив культуру бактерий и затем излечившись антибиотиками. Результаты совместных изысканий Уоррен и Маршалл в 1983 году опубликовали в авторитетном журнале Lancet, и эта дата считается точкой отсчета в новой эпохе исследования язвенной болезни. В 1989 году бактерия была окончательно идентифицирована и получила наименование Helicobacter pylori (греч. helix — спираль), в 1998 году расшифрован ее геном.
Считается, что носителями H. Pylori являются 50—60% людей. Но заболевают, понятное дело, не все, это зависит и от индивидуальной сопротивляемости организма, и от различий между отдельными штаммами H. pylori. С этой бактерией связывают 80—90% всех случаев язвенной болезни. Передается H. pylori через желудочно-кишечный тракт, так что старые добрые рекомендации тщательно мыть руки перед едой и не пить некипяченой воды по-прежнему актуальны.
А что же стресс и грубая пища — не имеют отношения к язве? Еще как имеют. Однако есть разница между причинным (этиологическим) фактором, условиями его реализации и механизмами развития нарушений (патогенезом). Установка или неустановка файрвола может иметь решающее значение как условие для заражения (или незаражения) компьютерным вирусом. Действия вируса в компьютере, его своевременное (или несвоевременное) обнаружение и уничтожение — факторы патогенеза. Но этиологический-то фактор, вокруг которого все закручивается, — сам вирус.
Диагностика наличия в организме H. pylori не всегда требует проведения биопсии. В ряде случаев бывает достаточно теста, основанного на наличии у H. pylori уреазы — фермента, разлагающего мочевину на углекислый газ и аммиак. Используется мочевина, содержащая изотопы углерода 13С или, реже, 14С (радиоактивен), а анализ выдыхаемого воздуха на меченную углекислоту проводится масс-спектрометрическим способом. Не требуют эндоскопического вмешательства также иммунологические методы и определение ДНК возбудителя в фекалиях. Сейчас уже отточены стандартные схемы полного уничтожения (эрадикации) H. pylori в организме, всегда включающие препараты нескольких групп (блокаторы протоновой помпы, препараты висмута и антибактериальные средства).
Проблема хронического гастрита и язвы, конечно, не сводится лишь к H. pylori. Но и роль обнаруженного возбудителя, похоже, не ограничивается этими болезнями. Речь идет не только о раке желудка, который может развиваться как результат длительной вялотекущей язвенной болезни, и значение H. pylori здесь доказано. Есть данные о связи этого инфекционного агента с атеросклерозом, ишемической болезнью сердца и инсультом. В общем, Нобелевку зазря не дадут.
ТЕМА НОМЕРА: Архитектура CPU
В 70-х годах прошлого столетия проектирование и изготовление центральных процессоров было занятием, принципиально доступным каждому. Если какому-нибудь сотруднику, скажем, Стэндфордского университета приходила в голову интересная идея, он мог легко набрать команду, основать фирму, найти инвесторов и уже через год-два выбросить на рынок свои CPU.
Через тридцать с небольшим лет процессоры усложнились до такой степени, что разработка хоть сколько-нибудь быстрого по современным меркам кристалла требует огромной армии инженеров, гигантских инвестиций и целого моря времени. И дело здесь отнюдь не в тонких кремниевых технологиях и стоящих миллиарды долларов полупроводниковых фабриках — просто уже в 80-х годах разработка принципиально нового CPU требовала двух-трех, а в 90-х — пяти-шести лет напряженной работы. Те же китайцы, даже располагая подробной информацией о тридцатилетней истории проектирования процессоров, владея новейшими фабриками по производству кремниевых кристаллов и не стремясь изобретать что-то новое, потратили на разработку собственного простейшего MIPS32-подобного процессора Godson (примерно эквивалентного по производительности i486) несколько лет. Это не считая еще одного года, когда новый кристалл отлаживали. А на разработку MIPS64-подобной архитектуры с приемлемой производительностью (~Pentium III 500—600 МГц) у китайской Академии наук ушло еще четыре года, — четыре года, потраченных только на то, чтобы повторить успех более чем двенадцатилетней давности. Но почему все получается так сложно? Чтобы ответить на этот вопрос нам придется вернуться на 30-40 лет в прошлое
Шаг первый. CISC
Так уж исторически сложилось, что поначалу совершенствование процессоров было направлено на то, чтобы сконструировать по возможности более функциональный компьютер, который позволил бы выполнять как можно больше разных инструкций. Во-первых, так было удобнее для программистов (компиляторы языков высокого уровня еще только начинали развиваться, и все по-настоящему важные программы писались на ассемблере), а во-вторых, использование сложных инструкций зачастую позволяло сильно сократить размеры написанной на ассемблере программы. А где меньше инструкций — меньше и затраченное на исполнение программы время.
Надо признать, что достигнутые на этом пути успехи действительно впечатляли — в последних версиях ЭВМ выразительность ассемблерного листинга зачастую не уступала выразительности программы, написанной на языке высокого уровня. Одной-единственной машинной инструкцией можно было сказать практически все, что угодно. К примеру, такие машины, как DEC VAX, аппаратно поддерживали инструкции «добавить элемент в очередь», «удалить элемент из очереди» и даже «провести интерполяцию полиномом» (!); а знаменитое семейство процессоров Motorola 68k почти для всех инструкций поддерживало до двенадцати (!) режимов адресации памяти, вплоть до взятия в качестве аргумента инструкции «данных, записанных по адресу, записанному вон в том регистре, со смещением, записанным вот в этом регистре». Отсюда и общее название соответствующих архитектур: CISC — Complex Instruction Set Computers («компьютеры с набором инструкций на все случаи жизни»).
На практике это привело к тому, что подобные инструкции оказалось сложно не только выполнять, но и просто декодировать (выделять из машинного кода новую инструкцию и отправлять ее на исполнительные устройства). Чтобы машинный код CISC-компьютеров из-за сложных инструкций не разрастался до огромного размера, машинные инструкции в большинстве этих архитектур имели неоднородную структуру (разное расположение и размеры кода операции и ее операндов) и сильно отличающуюся длину (в x86, например, длина инструкций варьируется от 1 до 15 байт). Еще одной проблемой стало то, что при сохранении приемлемой сложности процессора многие инструкции оказалось принципиально невозможно выполнить «чисто аппаратно», и поздние CISC-процессоры были вынуждены обзавестись специальными блоками, которые «на лету» заменяли некоторые сложные команды на последовательности более простых. В результате все CISC-процессоры оказались весьма трудоемкими в проектировании и изготовлении. Но что самое печальное, к моменту расцвета CISC-архитектур стало ясно, что все эти конструкции изобретались в общем-то зря — исследования программного обеспечения того времени, проведенные IBM, наглядно показали, что даже программисты, пишущие на ассемблере, все эти «сверхвозможности» почти никогда не использовали, а компиляторы языков высокого уровня — и не пытались использовать.
К началу 80-х годов классические CISC полностью исчерпали себя. Расширять набор инструкций в рамках этого подхода дальше не имело смысла, наоборот — технологи столкнулись с тем, что из-за высокой сложности CISC-процессоров оказалось трудно наращивать их тактовую частоту, а из-за «тормознутости» оперативной памяти тех времен зашитые в память процессора расшифровки сложных инструкций зачастую работают медленнее, чем точно такие же цепочки команд, встречающиеся в основной программе. Короче говоря, стало очевидным, что CISC-процессоры нужно упрощать — и на свет появился RISC, Reduced Instruction Set Computer.
Регистровые окна SPARC
При построении RISC-процессоров принимается во внимание медлительность оперативной памяти. Обращения к ней (даже с учетом различных кэшей) — «дорогостоящи» и требуют дополнительных вычислений, а потому, насколько это возможно, их следует избегать. Но Load/Store-архитектура и большое число регистров — не единственное, что можно сделать.
В любом программном коде можно встретить немало вызовов функций — фактически требований к процессору перейти в заданное место программы, продолжить выполнение программы до специальной инструкции возврата, после чего — вернуться к тому месту, где произошел вызов, почти полностью восстановив свое состояние до вызова функции. Чтобы это можно было сделать, при вызове функции процессор должен «запомнить» свое текущее состояние — в частности, содержимое некоторых регистров общего назначения и значительной части специальных регистров. Традиционное решение — «запихнуть» все необходимые данные в специальную конструкцию — стек[ Стек можно условно представить как запаянную с одного конца трубку, в которую по одному кладутся и по одному же извлекаются шарики (данные). Первый положенный в трубку шарик извлекается последним, так что если мы, скажем, по очереди положим (push) в стек числа 1, 2, 3, то извлекая (pop) данные, мы поочередно достанем 3, 2, 1.], которую процессор поддерживает на аппаратном уровне и которая в большинстве процессоров реализована в виде пары служебных регистров и выделенного участка оперативной памяти, куда все складываемые в стек данные и записываются. Поэтому любой вызов функции в традиционной схеме неявным образом приводит к записи в оперативную память десятков, а то и сотен байт информации. Есть даже целый ряд модельных задачек на эту тему — как написать компилятор, минимизирующий количество сохраняемой информации; причем кое-какие из этих наработок поддерживаются популярными компиляторами (например, соглашение __fastcall в некоторых компиляторах C/C++). Но оказывается, что всего этого можно избежать.
В типичной SPARC-архитектуре используется регистровый файл из 128 регистров; причем пользователю из них одномоментно доступны только расположенные подряд 24 регистра, образующие в этом файле окно, плюс еще восемь стоящих особняком глобальных регистров. Глобальные регистры используются для глобальных переменных[В структурных языках программирования типа C принято разделять локальные переменные, которые определены и используются только одной конкретной функцией и существуют только то время, пока эта функция работает; и глобальные переменные, которые существуют все время, пока выполняется программа, и доступны всем функциям программы]; регистровое окно — для локальных. Когда нам нужно вызвать какую-нибудь функцию, мы записываем необходимые для ее работы исходные данные в конец окна, а процессор при вызове функции попросту смещает окно по регистровому файлу таким образом, чтобы записанные данные оказались в начале нового, пока пустого окна. Требовавшие сохранения временные данные вызывавшего функцию кода оказываются за пределами окна, так что испортить их нечаянными действиями невозможно. А когда функция заканчивает работу, то полученные результаты записываются в те же самые регистры в начале окна, после чего процессор смещает его обратно. И никаких расходов на сохранение-восстановление стека.
Расположение окон в SPARC’ах можно программировать, добиваясь максимально эффективного использования схемы (либо много окон, но маленьких, либо мало — но больших; в зависимости от того, что за функции встречаются в программе) — этот факт даже отражен в названии процессора (Scalable Processor ARChitecture). Подобно многим своим RISC-сестрам, разработанная в середине 80-х годов и пережившая расцвет в середине 90-х, SPARC-архитектура не выдержала «гонки мегагерц» и сегодня фактически умерла. Но предложенный ею подход живет и здравствует — его позволяет использовать, например, архитектура IA-64 (Itanium).
Шаг второй. RISC
Точно так же, как когда-то CISC-процессоры проектировались под нужды asm-программистов, RISC проектировался в расчете на типовой код, генерируемый компиляторами. Для начала разработчики свели к минимуму набор инструкций и к абсолютному минимуму — количество режимов адресации памяти; упаковав все, что осталось, в простой и удобный для декодирования регулярный машинный код. В частности, в классическом варианте RISC из инструкций, обращающихся к оперативной памяти, оставлены только две (Load — загрузить данные в регистр и Store — сохранить данные из регистра; так называемая Load/Store-архитектура), и нет ни одной инструкции вроде вычисления синуса, косинуса или квадратного корня (их можно реализовать «вручную»), не говоря уже о более сложных[Канонический пример — инструкция INDEX, выполнявшаяся на VAX медленнее, чем вручную написанный цикл, выполняющий ровно тот же объем работы]. Да что там синус с косинусом — в некоторых RISC-процессорах пытались отказаться даже от трудно реализуемого аппаратного умножения и деления! Правда, до таких крайностей ни один коммерческий RISC, к счастью, не дошел, но как минимум две попытки (ранние варианты MIPS и SPARC) предприняты были.
Второе важное усовершенствование RISC-процессоров, целиком вытекающее из Load/Store-архитектуры, — увеличение числа GPR (регистров общего назначения). Варианты, у которых меньше шестнадцати GPR, — большая редкость, причем почти все эти регистры полностью равноправны, что позволяет компилятору свободно распоряжаться ими, сохраняя большую часть промежуточных данных именно там, а не в стеке или оперативной памяти. В некоторых архитектурах, типа SPARC, «регистровость» возведена в абсолют, в некоторых — оставлена на разумном уровне; однако почти любой RISC-процессор обладает куда большим набором регистров, чем даже самый продвинутый CISC. Для сравнения: в классическом x86 IA-32 всего восемь регистров общего назначения, причем каждому из них приписано то или иное «специальное назначение» (скажем, в ESP хранится указатель на стек) затрудняющее или делающее невозможным его использование.
Среди прочих усовершенствований, внесенных в RISC, — такие нетривиальные идеи, как условные инструкции ARM или режимы работы команд[Например, некий модификатор в архитектуре PowerPC и некоторых других показывает, должна ли инструкция выставлять по результатам своего выполнения определенные флаги, которые потом может использовать инструкция условного перехода, или не должна. Это позволяет разнести в пространстве инструкцию, выполняющую вычисление условия, и инструкцию собственно условного перехода — что в конвейерных архитектурах зачастую позволяет процессору не «гадать», будет совершен переход или нет, а сразу достоверно это знать]. В классическом CISC они почти не встречаются, поскольку на момент разработки соответствующих наборов инструкций ценность этих решений была сомнительной (они выйдут на сцену только в конвейеризированных процессорах).
«В чистом виде» идею «легкого» RISC-процессора можно встретить у компании ARM с ее невероятно простыми и тем не менее весьма эффективными 32-разрядными CPU. Но простота далеко не главный показатель в процессоре, и как самоцель подход RISC в целом себя, наверное, не оправдал бы — резко уменьшившаяся сложность CPU и сопутствующее увеличение тактовой частоты и ускорение исполнения инструкций хорошо уравновешивались возросшими размерами программ и сильно упавшей их вычислительной плотностью[Среднее количество вычислений на единицу длины машинного кода]. К счастью, в то же время, когда начались разработки первых коммерческих RISC-процессоров, был сделан…
Условные инструкции 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 (блоки, собственно исполняющие инструкции).
Необходимость наличия системы сброса процессора. Поскольку операции FETCH и EXECUTE всегда выделены в отдельные стадии конвейера, то в тех случаях, когда в программном коде происходит разветвление (условный переход), зачастую оказывается, что по какой из веток пойти — пока неизвестно: инструкция, вычисляющая код условия, еще не выполнена[Вот здесь-то и нужны те самые режимы выставления флагов PowerPC, о которых шла речь в сноске 2]. В результате процессор вынужден либо приостанавливать выборку новых инструкций до тех пор, пока не будет вычислен код условия (а это может занять очень много времени и в типичном цикле страшно затормозит процессор), либо, руководствуясь соображениями блока предсказания переходов, «угадывать», какой из переходов скорее всего окажется правильным.
Наконец, конвейер обычно требует наличия специального планировщика (scheduler), призванного решать конфликты по данным. Если в программе идет зависимая цепочка инструкций (когда инструкция-2, следующая за инструкцией-1, использует для своих вычислений данные, только что вычисленные инструкцией-1), а время исполнения одной инструкции (от момента запуска на стадию EXECUTE и до записи полученных результатов в регистры) превосходит один такт, то мы вынуждены придержать выполнение очередной инструкции до тех пор, пока не будет полностью выполнена ее предшественница.