В архитектуре компьютера переименование регистров — это метод, который абстрагирует логические регистры от физических регистров. Каждый логический регистр имеет набор физических регистров, связанных с ним. Когда инструкция машинного языка ссылается на определенный логический регистр, процессор транспонирует это имя в один конкретный физический регистр на лету. Физические регистры непрозрачны и на них нельзя ссылаться напрямую, а только через канонические имена.
Эта техника используется для устранения ложных зависимостей данных , возникающих из-за повторного использования регистров последовательными инструкциями , которые не имеют реальных зависимостей данных между собой. Устранение этих ложных зависимостей данных выявляет больше параллелизма на уровне инструкций в потоке инструкций, который может быть использован различными и дополнительными методами, такими как суперскалярное и внеочередное выполнение для лучшей производительности .
Программы состоят из инструкций, которые оперируют значениями. Инструкции должны называть эти значения, чтобы отличать их друг от друга. Типичная инструкция может быть такой: сложить и и поместить результат в . В этой инструкции , и являются именами мест хранения.
Обычно значения, которыми манипулируют, используются несколько раз подряд. Регистровые машины используют это преимущество, вводя ряд регистров процессора , которые являются высокоскоростными ячейками памяти, в которых хранятся эти значения. Инструкции, которые используют регистры в качестве промежуточных значений, будут выполняться намного быстрее, чем те, которые обращаются к основной памяти . Это увеличение производительности является ключевым элементом дизайна процессора RISC , который использует регистры для всех своих основных математических и логических инструкций. Набор регистров в конкретной конструкции известен как его регистровый файл .
Отдельные регистры в файле обозначаются номером в машинном коде . Кодирование числа в машинном коде требует нескольких бит. Например, в Zilog Z80 в файле было восемь регистров общего назначения. Для выбора одного из восьми значений требуется три бита, так как 2 3 = 8. Большее количество регистров в файле приведет к лучшей производительности, так как в файле можно хранить больше временных значений и, таким образом, избегать дорогостоящих операций сохранения или загрузки из памяти. Как правило, более современные процессоры и процессоры с большими словами инструкций будут использовать больше регистров, когда это возможно. Например, архитектура набора инструкций IA-32 имеет 8 регистров общего назначения, x86-64 имеет 16, многие RISC имеют 32, а IA-64 имеет 128.
Преимущества большего регистрового файла компенсируются необходимостью использовать больше бит для кодирования номера регистра. Например, в системе, использующей 32-битные инструкции, вам может понадобиться три регистра, чтобы можно было выполнять операции типа = . Если регистровый файл содержит 32 записи, каждая из ссылок потребует 5 бит, и таким образом набор из трех регистров займет 15 бит, оставляя 17 для кодирования операции и другой информации. Расширение регистрового файла до 64 записей потребует 6 бит, всего 18 бит. Хотя это может привести к более высокой производительности, это также означает, что для кодирования инструкции останется меньше бит. Это приводит к попытке сбалансировать размер файла с количеством возможных инструкций.
Ранние компьютеры часто работали в тесном контакте с основной памятью, что снижало преимущества больших регистровых файлов. Распространенным замечанием по дизайну на рынке мини-компьютеров 1960-х годов было то, что регистры были физически реализованы в основной памяти, и в этом случае преимущество производительности заключалось просто в том, что инструкция могла напрямую ссылаться на местоположение, а не использовать второй байт или два для указания полного адреса памяти. Это делало инструкции меньше и, следовательно, быстрее для чтения. Такой дизайн, который максимизировал производительность путем тщательной настройки набора инструкций для минимального размера, был распространен до 1980-х годов. Примером такого подхода является MOS 6502 , у которого был только один регистр, в этом случае он назывался аккумулятором , и специальный режим адресации «нулевая страница», который обрабатывал первые 256 байтов памяти так, как если бы они были регистрами. Размещение кода и данных на нулевой странице означало, что инструкция была длиной всего два байта вместо трех, что значительно повышало производительность за счет избегания чтений.
Широкое внедрение динамической оперативной памяти в 1970-х годах изменило этот подход. Со временем производительность центральных процессоров (ЦП) возросла относительно памяти, к которой они были подключены, и стало неразумно использовать основную память в качестве регистров. Это привело к увеличению размера регистровых файлов, внутренних для ЦП, чтобы избегать обращения к памяти везде, где это возможно. Однако на практике невозможно полностью избежать доступа к памяти, и по мере того, как разница в скорости росла, каждый такой доступ становился все более и более дорогим с точки зрения количества инструкций, которые могли бы быть выполнены, если бы значение находилось в регистре.
Различные инструкции могут занимать разное количество времени; например, процессор может выполнять сотни инструкций «регистр-регистр», пока выполняется одна загрузка из основной памяти. Ключевым достижением в повышении производительности является возможность выполнять эти быстрые инструкции, пока другие ждут данных. Это означает, что инструкции больше не выполняются в том порядке, в котором они указаны в машинном коде, вместо этого они выполняются вне очереди .
Рассмотрим этот фрагмент кода, работающий на процессоре, работающем не по порядку:
r1 ≔ m [ 1024 ] ;прочитать значение в ячейке памяти 1024 r1 ≔ r1 + 2 ;добавьте два к значению m [ 1032 ] ≔ r1 ;сохранить результат в ячейке 1032 r1 ≔ m [ 2048 ] ;прочитать значение в 2048 r1 ≔ r1 + 4 ;добавляем 4 m [ 2056 ] ≔ r1 ;сохранить в 2056
Инструкции в последних трех строках независимы от первых трех инструкций, но процессор не может завершить выполнение , пока не будут завершены предыдущие , поскольку это приведет к добавлению четырех к значению 1024, а не 2048.r1 ≔ m[2048]
m[1032] ≔ r1
Если доступен другой регистр, это ограничение можно устранить, выбрав разные регистры для первых трех и вторых трех инструкций:
r1 ≔ м [ 1024 ] р1 ≔ р1 + 2 м [ 1032 ] ≔ r1 r2 ≔ м [ 2048 ] р2 ≔ р2 + 4 м [ 2056 ] ≔ r2
Теперь последние три инструкции могут выполняться параллельно с первыми тремя. Программа будет работать быстрее, чем раньше, устраняя зависимость от данных, вызванную ненужным использованием одного и того же регистра в обеих последовательностях. Компилятор может обнаруживать независимые последовательности инструкций и, если есть регистры, доступные для использования, выбирать разные регистры во время распределения регистров в процессе генерации кода .
Однако для ускорения кода, сгенерированного компиляторами, которые не выполняют эту оптимизацию, или кода, для которого не хватает регистров для выполнения этой оптимизации, многие высокопроизводительные процессоры предоставляют файл регистров с большим количеством регистров, чем указано в наборе инструкций, и на аппаратном уровне переименовывают ссылки в регистрах, определенных набором инструкций, чтобы ссылаться на регистры в файле регистров, так что исходная последовательность инструкций, использующая только r1, ведет себя так, как если бы она была:
рА ≔ м [ 1024 ] рА ≔ рА + 2 м [ 1032 ] ≔ рА rB ≔ м [ 2048 ] рБ ≔ рБ + 4 м [ 2056 ] ≔ рБ
с регистром r1, "переименованным" во внутренний регистр rA для первых трех инструкций и во внутренний регистр rB для вторых трех инструкций. Это устраняет ложную зависимость данных, позволяя первым трем инструкциям выполняться параллельно со вторыми тремя инструкциями.
Когда более чем одна инструкция ссылается на определенное местоположение как на операнд, либо путем чтения его (как вход), либо путем записи в него (как выход), выполнение этих инструкций в порядке, отличном от исходного порядка программы, может привести к трем видам опасностей, связанных с данными:
Вместо того, чтобы откладывать запись до завершения всех чтений, можно поддерживать две копии местоположения, старое значение и новое значение. Чтения, которые предшествуют, в программном порядке, записи нового значения, могут быть предоставлены старым значением, даже в то время как другие чтения, которые следуют за записью, предоставляются новым значением. Ложная зависимость нарушается, и создаются дополнительные возможности для выполнения вне очереди. Когда все чтения, которым требуется старое значение, были удовлетворены, его можно отбросить. Это основная концепция переименования регистров.
Все, что читается и пишется, может быть переименовано. Хотя регистры общего назначения и регистры с плавающей точкой обсуждаются чаще всего, флаговые и статусные регистры или даже отдельные статусные биты также часто переименовываются.
Ячейки памяти также могут быть переименованы, хотя это обычно не делается в той степени, которая практикуется при переименовании регистров. Буфер хранения с гейтом процессора Transmeta Crusoe является формой переименования памяти.
Если бы программы воздерживались от повторного использования регистров немедленно, не было бы необходимости в переименовании регистров. Некоторые наборы инструкций (например, IA-64 ) указывают очень большое количество регистров именно по этой причине. Однако существуют ограничения этого подхода:
Увеличение размера кода важно, поскольку при увеличении размера программного кода кэш инструкций чаще пропускает инструкции, и процессор останавливается в ожидании новых инструкций.
Программы на машинном языке определяют чтение и запись в ограниченный набор регистров, заданный архитектурой набора инструкций (ISA). Например, Alpha ISA определяет 32 целочисленных регистра, каждый шириной 64 бита, и 32 регистра с плавающей точкой, каждый шириной 64 бита. Это архитектурные регистры. Программы, написанные для процессоров, работающих с набором инструкций Alpha, будут определять операции чтения и записи этих 64 регистров. Если программист остановит программу в отладчике, он может наблюдать содержимое этих 64 регистров (и нескольких регистров состояния), чтобы определить ход работы машины.
Один конкретный процессор, реализующий эту ISA, Alpha 21264 , имеет 80 целочисленных и 72 плавающих физических регистра. На чипе Alpha 21264 есть 80 физически отдельных ячеек, которые могут хранить результаты целочисленных операций, и 72 ячейки, которые могут хранить результаты операций с плавающей точкой (на самом деле, ячеек даже больше, но эти дополнительные ячейки не имеют отношения к операции переименования регистров).
В следующем тексте описываются два стиля переименования регистров, которые различаются схемой, которая хранит данные, готовые для исполнительного устройства.
Во всех схемах переименования машина преобразует архитектурные регистры, на которые ссылается поток инструкций, в теги. Там, где архитектурные регистры могут быть указаны 3–5 битами, теги обычно представляют собой 6–8-битные числа. Файл переименования должен иметь порт чтения для каждого входа каждой инструкции, переименовываемой в каждом цикле, и порт записи для каждого выхода каждой инструкции, переименовываемой в каждом цикле. Поскольку размер файла регистров обычно растет как квадрат числа портов, файл переименования обычно физически большой и потребляет значительную мощность.
В стиле индексированного по тегам регистрового файла есть один большой регистровый файл для значений данных, содержащий один регистр для каждого тега. Например, если машина имеет 80 физических регистров, то она будет использовать 7 битовых тегов. 48 возможных значений тегов в этом случае не используются. В этом стиле, когда инструкция выдается исполнительному блоку, теги исходных регистров отправляются в физический регистровый файл, где значения, соответствующие этим тегам, считываются и отправляются исполнительному блоку.
В стиле станции резервирования есть много небольших ассоциативных регистровых файлов, обычно по одному на входах каждого исполнительного блока. Каждый операнд каждой инструкции в очереди выдачи имеет место для значения в одном из этих регистровых файлов. В этом стиле, когда инструкция выдается исполнительному блоку, записи регистрового файла, соответствующие записи очереди выдачи, считываются и пересылаются исполнительному блоку.
Этот стиль переименования используется в MIPS R10000 , Alpha 21264 и в разделе FP процессора AMD Athlon .
На этапе переименования каждый архитектурный регистр, на который ссылаются (для чтения или записи), ищется в архитектурно-индексированном файле переназначения . Этот файл возвращает тег и бит готовности. Тег не готов, если есть поставленная в очередь инструкция, которая будет записывать в него, но еще не выполнена. Для операндов чтения этот тег занимает место архитектурного регистра в инструкции. Для каждой записи регистра новый тег извлекается из свободного тегового FIFO, и новое отображение записывается в файл переназначения, так что будущие инструкции, считывающие архитектурный регистр, будут ссылаться на этот новый тег. Тег помечается как неготовый, поскольку инструкция еще не выполнена. Предыдущий физический регистр, выделенный для этого архитектурного регистра, сохраняется вместе с инструкцией в буфере переупорядочивания , который представляет собой FIFO, который хранит инструкции в программном порядке между этапами декодирования и градуировки.
Затем инструкции помещаются в различные очереди выдачи . По мере выполнения инструкций теги для их результатов транслируются, и очереди выдачи сопоставляют эти теги с тегами их неготовых исходных операндов. Совпадение означает, что операнд готов. Файл переназначения также сопоставляет эти теги, так что он может пометить соответствующие физические регистры как готовые. Когда все операнды инструкции в очереди выдачи готовы, эта инструкция готова к выдаче. Очереди выдачи выбирают готовые инструкции для отправки различным функциональным блокам в каждом цикле. Неготовые инструкции остаются в очередях выдачи. Это неупорядоченное удаление инструкций из очередей выдачи может сделать их большими и энергозатратными.
Выданные инструкции считываются из индексированного по тегу физического регистрового файла (минуя только что переданные операнды) и затем выполняются. Результаты выполнения записываются в индексированный по тегу физический регистровый файл, а также транслируются в обходную сеть, предшествующую каждому функциональному блоку. Градация помещает предыдущий тег для записанного архитектурного регистра в свободную очередь, чтобы его можно было повторно использовать для новой декодированной инструкции.
Исключение или неверное предсказание ветвления приводит к тому, что файл перераспределения возвращается к состоянию перераспределения при последней допустимой инструкции посредством комбинации снимков состояния и циклического прохождения предыдущих тегов в очереди предварительной градуировки в порядке возрастания. Поскольку этот механизм необходим и поскольку он может восстановить любое состояние перераспределения (а не только состояние до инструкции, которая в данный момент градуируется), неверное предсказание ветвления может быть обработано до того, как ветвь достигнет градуировки, потенциально скрывая задержку неверного предсказания ветвления.
Этот стиль используется в целочисленной части проектов AMD K7 и K8.
На этапе переименования каждый архитектурный регистр, на который ссылаются для чтения, ищется как в архитектурно-индексированном файле будущего , так и в файле переименования. Чтение файла будущего дает значение этого регистра, если еще нет невыполненной инструкции для записи в него (т. е. он готов). Когда инструкция помещается в очередь выдачи, значения, считанные из файла будущего, записываются в соответствующие записи на станциях резервирования. Записи регистров в инструкции приводят к записи нового, неготового тега в файл переименования. Номер тега обычно последовательно выделяется в порядке инструкций — нет необходимости в свободном теговом FIFO.
Как и в случае с индексированной тегами схемой, очереди вопросов ждут неготовых операндов, чтобы увидеть соответствующие трансляции тегов. В отличие от индексированной тегами схемы, соответствующие теги вызывают запись соответствующего значения трансляции в станцию резервирования записи очереди вопросов.
Выданные инструкции считывают свои аргументы со станции резервирования, обходят только что переданные операнды и затем выполняют. Как упоминалось ранее, файлы регистров станции резервирования обычно небольшие, возможно, с восемью записями.
Результаты выполнения записываются в буфер переупорядочивания , на станции резервирования (если запись в очереди выдачи имеет соответствующий тег) и в файл будущих задач, если это последняя инструкция, нацеленная на этот архитектурный регистр (в этом случае регистр помечается как готовый).
Graduation копирует значение из буфера переупорядочивания в файл архитектурного регистра. Единственное применение файла архитектурного регистра — восстановление после исключений и неверных предсказаний ветвлений.
Исключения и неверные предсказания ветвлений, распознанные при градуировке, приводят к копированию архитектурного файла в будущий файл, а все регистры помечаются как готовые в файле переименования. Обычно нет способа восстановить состояние будущего файла для некоторой промежуточной инструкции между декодированием и градуировкой, поэтому обычно нет способа выполнить раннее восстановление после неверных предсказаний ветвлений.
В обеих схемах инструкции вставляются в очереди проблем по порядку, но удаляются вне очереди. Если очереди не сворачивают пустые слоты, то они либо будут иметь много неиспользуемых записей, либо потребуют некоторого вида переменного приоритетного кодирования для случаев, когда несколько инструкций одновременно готовы к выполнению. Очереди, которые сворачивают дыры, имеют более простое приоритетное кодирование, но требуют простой, но большой схемы для продвижения инструкций по очереди.
Резервирующие станции имеют лучшую задержку от переименования до выполнения, поскольку этап переименования находит значения регистров напрямую, а не находит физический номер регистра, а затем использует его для нахождения значения. Эта задержка проявляется как компонент задержки неверного предсказания ветвления.
Станции резервирования также имеют лучшую задержку от выдачи инструкции до ее выполнения, поскольку каждый локальный файл регистра меньше, чем большой центральный файл схемы с индексацией тегов. Генерация тегов и обработка исключений также проще в схеме станции резервирования, как обсуждается ниже.
Физические файлы регистров, используемые станциями резервирования, обычно сворачивают неиспользуемые записи параллельно с обслуживаемой ими очередью выдачи, что делает эти файлы регистров больше в совокупности, потребляет больше энергии и сложнее, чем более простые файлы регистров, используемые в схеме с индексацией по тегам. Хуже того, каждая запись в каждой станции резервирования может быть записана каждой шиной результатов, так что машина станции резервирования с, например, 8 записями очереди выдачи на функциональный блок обычно будет иметь в 9 раз больше обходных сетей, чем эквивалентная машина с индексацией по тегам. Следовательно, пересылка результатов потребляет гораздо больше энергии и площади, чем в схеме с индексацией по тегам.
Более того, схема станции резервирования имеет четыре места (Future File, Reservation Station, Reorder Buffer и Architectural File), где может быть сохранено значение результата, тогда как схема с индексацией тегов имеет только одно (физический регистровый файл). Поскольку результаты от функциональных блоков, транслируемые во все эти места хранения, должны достигать гораздо большего количества мест в машине, чем в схеме с индексацией тегов, эта функция потребляет больше энергии, площади и времени. Тем не менее, в машинах, оснащенных очень точными схемами прогнозирования ветвлений, и если задержки выполнения являются серьезной проблемой, станции резервирования могут работать на удивление хорошо.
IBM System/360 Model 91 была одной из первых машин, которая поддерживала нестандартное выполнение инструкций; она использовала алгоритм Томасуло , который использует переименование регистров.
POWER1 1990 года — первый микропроцессор , использовавший переименование регистров и внеочередное выполнение. Этот процессор реализовал переименование регистров только для загрузки с плавающей точкой. У POWER1 был только один FPU, поэтому использование переименования для инструкций с плавающей точкой, кроме операций с памятью, было ненужным. У POWER2 было несколько FPU, поэтому переименование использовалось для всех инструкций с плавающей точкой. [1]
Первоначальный дизайн R10000 не имел ни схлопывающихся очередей проблем, ни кодирования с переменным приоритетом, и в результате страдал от проблем с голоданием — самая старая инструкция в очереди иногда не выдавалась до тех пор, пока обе инструкции декодирования не останавливались полностью из-за отсутствия регистров переименования, и все остальные инструкции не были выданы. Более поздние версии дизайна, начиная с R12000, использовали частично переменный приоритетный кодер для смягчения этой проблемы.
Ранние машины с неупорядоченными операциями не разделяли функции переименования и хранения ROB/PRF. Кстати, некоторые из самых ранних, такие как RUU Sohi или Metaflow DCAF, объединяли планирование, переименование и хранение в одной структуре.
Большинство современных машин выполняют переименование путем индексации ОЗУ таблицы карт с логическим номером регистра. Например, P6 делал это; будущие файлы делают это и имеют хранилище данных в той же структуре.
Однако более ранние машины использовали в переименовывателе память с адресацией по содержимому (CAM). Например, HPSM RAT или Register Alias Table, по сути, использовала CAM на логическом номере регистра в сочетании с различными версиями регистра.
Во многих отношениях история неисправной микроархитектуры была тем, как эти CAM-ы постепенно устранялись. Маленькие CAM-ы полезны; большие CAM-ы непрактичны. [ необходима цитата ]
Микроархитектура P6 была первой микроархитектурой Intel, которая реализовала как внеочередное выполнение, так и переименование регистров. Микроархитектура P6 использовалась в микропроцессорах Pentium Pro, Pentium II, Pentium III, Pentium M, Core и Core 2. Cyrix M1 , выпущенный 2 октября 1995 года, [2] был первым процессором x86, который использовал переименование регистров и внеочередное выполнение. Другие процессоры x86 (такие как NexGen Nx686 и AMD K5 ), выпущенные в 1996 году, также имели переименование регистров и внеочередное выполнение RISC μ-операций (вместо собственных инструкций x86). [3] [4]