Хабрахабр

[Перевод] Как разрабатываются и производятся процессоры: основы архитектуры компьютеров

image

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

Если вам было интересно разобраться в подробностях работы процессоров, то начинать изучение лучше с этой серии статей. В этой серии мы расскажем об компьютерной архитектуре, проектировании процессорных плат, VLSI (very-large-scale integration), производстве чипов и тенденциях будущего в области вычислительной техники.

В том числе мы рассмотрим процессорные ядра, иерархию памяти, предсказание ветвлений и другое. Мы начнём с очень высокоуровневого объяснения того, чем занимается процессор и как строительные блоки соединяются в функционирующую конструкцию. Простейшее объяснение: процессор следует набору инструкций для выполнения определённой операции над множеством входящий данных. Во-первых, нам нужно дать простое определение тому, что делает ЦП. Это может быть и нечто более сложное, например, деление двух чисел, если результат предыдущего вычисления больше нуля. Например, это может быть считывание значения из памяти, затем прибавление его к другому значению, и наконец сохранение результата в память по другому адресу.

Эти инструкции загружаются из памяти и в простом процессоре выполняются одна за другой, пока программа не завершится. Программы, например, операционная система или игра, сами по себе являются последовательностями инструкций, которые должен выполнять ЦП. Он понимает только единицы и нули, поэтому нам нужно каким-то образом представить код в этом формате.
Разработчики программного обеспечения пишут программы на высокоуровневых языках, например, на C++ или на Python, но процессор не может их понимать.

Программы компилируются в набор низкоуровневых инструкций, называемых языком ассемблера, который является частью архитектуры набора команд (Instruction Set Architecture, ISA). Это набор команд, которые должен понимать и выполнять ЦП. Одними из наиболее распространённых ISA являются x86, MIPS, ARM, RISC-V и PowerPC. Точно так же, как синтаксис написания функции на C++ отличается от функции, выполняющей то же действие в Python, у каждой ISA есть свой отличающийся синтаксис.

ISA RISC-V использует инструкции с фиксированной длиной, и это означает, что определённое заранее заданное количество битов в каждой инструкции определяет, какой тип имеет эта инструкция. Эти ISA можно разбить на две основных категории: с фиксированной и с переменной длиной. В x86 инструкции могут кодироваться различным способом с разным количеством битов для разных частей. В x86 всё иначе, в нём используются инструкции с переменной длиной. Из-за такой сложности декодер инструкций в процессоре x86 обычно является самой сложной частью всей устройства.

В то время, как у популярных версий архитектуры RISC-V есть примерно 100 инструкций и все они имеют открытый исходный код, архитектура x86 проприетарна и никто не знает, сколько всего инструкций в ней есть. Инструкции с фиксированной длиной обеспечивают простое декодирование благодаря постоянной структуре, но ограничивают общее количество инструкций, которые могут поддерживаться ISA. Несмотря на различия между ISA, по сути все они имеют одинаковый базовый функционал. Обычно считается, что существует несколько тысяч инструкций x86, но точное число никто не публикует.

Пример некоторых инструкций RISC-V. Опкод справа имеет длину 7 бит и определяет тип инструкции. Кроме того, каждая инструкция содержит биты, определяющие используемые регистры и выполняемые функции. Так ассемблерные инструкции разбиваются на двоичный код, чтобы его понимал процессор.

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

На втором этапе инструкция декодируется, чтобы ЦП мог понять, какого типа эта инструкция. Первый этап — передача инструкции из памяти в процессор для начала выполнения. После того, как ЦП узнает, инструкцию какого типа он выполняет, операнды для инструкции берутся из памяти или внутренних регистров ЦП. Существует множество типов, в том числе арифметические инструкции, инструкции ветвления и инструкции памяти. Большинство современных процессоров являются 64-битными, то есть размер каждого значения данных составляет 64 бита. Если вы хотите сложить число A и число B, то не можете выполнять сложение, пока не знаете значений A и B.

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

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

Это означает, что хотя процессор начинает и завершает в каждом цикле несколько инструкций, может потребоваться 20 или больше циклов, чтобы выполнить одну инструкцию от начала до конца. Это объяснение, разумеется, сильно упрощено, и большинство современных процессоров для повышения эффективности разбивает эти несколько этапов на 20 или даже больше мелких этапов. Такая модель обычно называется pipeline («трубопровод», на русский обычно переводят как «конвейер»), потому что для заполнения трубопровода жидкостью и полного её прохождения требуется время, но после заполнения расход (вывод данных) будет постоянным.

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

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

Это означает, что любой момент времени процессор одновременно выполняет на каждом этапе конвейера множество инструкций. Кроме выполнения с изменением очерёдности современные процессоры применяют технологию под названием суперскалярная архитектура. Если процессор видит, что к выполнению готовы две инструкции, и между ними нет зависимости, то он не ждёт, пока они завершатся по отдельности, а выполняет их одновременно. Он может также ожидать ещё сотни других, чтобы начать их выполнение, и для того, чтобы иметь возможность одновременного выполнения нескольких инструкций внутри процессоров есть несколько копий каждого этапа конвейера. Процессоры Intel и AMD сейчас поддерживают двухсторонний SMT, а IBM разработала чипы, поддерживающие до восьми SMT. Одна из популярных реализаций такой архитектуры называется Simultaneous Multithreading (SMT) и также известна, как Hyper-Threading.

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

Но кэши отличаются задержкой и скоростью доступа. Необходимость кэшей иногда может сбивать с толку, ведь они хранят данные, как и ОЗУ или SSD. Для ответа с передачей данных ОЗУ может потребоваться сотни циклов, и процессору в это время будет нечем заняться. Даже несмотря на то, что память ОЗУ чрезвычайно быстра, она на порядки величин медленнее, чем нужно для ЦП. Без кэшей процессоры бы постоянно стопорились. А если данных нет в ОЗУ, то могут потребоваться десятки тысяч циклов для получения доступа к ним с SSD.

Кэш L1 — самый маленький и быстрый, L2 находится посередине, а L3 — самый крупный и медленный из всех кэшей. Обычно процессоры имеют три уровня кэша, образующих так называемую иерархию памяти. По порядку величин эти регистры являются самыми быстрыми устройствами хранения в системе. Выше кэшей в иерархии находятся мелкие регистры, хранящие во время вычислений единственное значение данных. Когда компилятор преобразует высокоуровневую программу в язык ассемблера, он определяет наилучший способ использования этих регистров.

Если да, то можно всего за пару циклов получить к ним доступ. Когда ЦП запрашивает данные из памяти, то сначала проверяет, хранятся ли эти данные уже в кэше L1. Кэши реализованы таким образом, что в общем случае они прозрачны для ядра. Если их там нет, то процессор проверяет L2, а затем и кэш L3. При переходе к последующим уровням в иерархии памяти размер и задержки обычно растут на порядки величин. Ядро просто запрашивает данные по указанному адресу памяти, и тот уровень в иерархии, на котором они есть, отвечает ему. В конце концов, если ЦП не находит данные ни в одном из кэшей, то обращается в основную память (ОЗУ).

В обычном процессоре каждое ядро имеет два кэша L1: один для данных и другой для инструкций. Кэши L1 обычно имеют в целом объём порядка 100 килобайт и размер очень варьируется в зависимости от чипа и поколения процессора. Кроме того, обычно для каждого ядра есть свой кэш L2, хотя в некоторых архитектурах он может быть общим для двух ядер. Кэши L2 обычно имеют размер несколько сотен килобайт. Наконец, есть единственный кэш L3, общий для всех ядер, имеющий размер порядка десятков мегабайт.

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

Инструкции переходов (ветвлений) схожи с конструкциями «if» для процессора. Кроме кэшей одним из самых важных строительных блоков современного процессора является точный предсказатель переходов. Например, нам нужно сравнить два числа, и если они равны, выполнить одну функцию, а если не равны, то выполнить другую. Один набор инструкций выполняется, если условие истинно, а другой — если оно ложно. Эти инструкции ветвления применяются чрезвычайно часто и могут составлять примерно 20% всех инструкций в программе.

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

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

Если он предсказывает неверно слишком часто, то начинает обучаться правильному поведению. Такие предсказатели перехода — одни из самых простейших разновидностей машинного обучения, потому что предсказатель изучает поведение ветвей в процессе выполнения. Десятилетия исследований методик предсказания переходов привели к тому, что в современных процессорах точность предсказаний превышает 90%.

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

Инновации и разработка продуманной структуры привели к повышению производительности и более оптимальному использованию аппаратных средств. За последние десятилетия используемая в современных процессорах архитектура прошла долгий путь. Тем не менее, фундаментальные принципы работы процессоров стандартированы для всех архитектур и моделей. Однако разработчики центральных процессоров тщательно хранят секреты их технологий, поэтому мы не можем точно узнать, что происходит у них внутри. Intel может добавлять свои секретные ингредиенты, чтобы повысить долю попаданий кэша, а AMD может добавить улучшенный предсказатель переходов, но процессоры обеих компаний выполняют одинаковую задачу.

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

Рекомендуемое чтение

Теги
Показать больше

Похожие статьи

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть