Использование того же регистра для вывода, что и для ввода, не имеет присущего недостатка для ARM 1 .Однако я не думаю, что в этом есть и какое-то внутреннее преимущество. В общем случае все может стать более интересным, когда мы говорим о записи регистров, которых инструкция уже не должна ждать (т. Е. Не вводит).
Используйте столько регистров, сколько необходимо для сохранения инструкций.( Имейте в виду соглашение о вызовах, хотя : если вы используете больше, чем r0..r3, вам придется сохранять / восстанавливать дополнительные, которые вы используете, если вы хотите вызывать свою функцию из C).В частности, обычно оптимизировать для наименьшего динамического количества команд;выполнение дополнительной настройки / очистки для сохранения инструкций внутри циклов обычно того стоит.
И не только для сохранения инструкций: программная конвейеризация для скрытия задержки загрузкипотенциально ценный на конвейерных процессорах исполнения по порядку.например, если вы зацикливаетесь на массиве, загрузите в регистр значение, которое вам понадобится через 2 итерации, и не трогайте его до тех пор.(Разверните цикл).Процессор по порядку может только запускать инструкции по порядку, но они могут потенциально завершаться не по порядку.например, загрузка, которая отсутствует в кэше, не останавливает процессор, пока вы не попытаетесь прочитать его, когда он не готов.Я думаю, вы можете предположить, что высокопроизводительные процессоры с порядком работы, такие как современные ARM, будут иметь все необходимые табло для отслеживания того, какие регистры ожидают готовности ALU или результата загрузки.
Без фактического перехода на полное программное обеспечение.При конвейерной обработке вы можете иногда получить аналогичные результаты, выполняя блок нагрузок, затем вещи, а затем блок магазинов.например, memcpy, оптимизированный для больших копий, может загрузить 12 регистров в своем основном развернутом цикле, а затем сохранить эти 12 регистров.Таким образом, расстояние между загрузкой и хранилищем одного и того же регистра все еще достаточно велико, чтобы скрыть задержку загрузки кэша L1 как минимум.
Текущие (?) Платы Raspberry Pi ( RPi 3+ ) использовать ARM Cortex-A53 , суперскалярную микроархитектуру порядка 2 в ширину.
Любое ядро ARM (например, Cortex-A57), которое превосходитдля выполнения заказа будет использоваться переименование регистра , чтобы исключить опасность WAW (запись после записи) и WAR.(https://en.wikipedia.org/wiki/Hazard_(computer_architecture)#Data_hazards).
На обычном ядре, таком как A53, WAR, безусловно, не проблема: более поздняя инструкция не может записать регистр до того, как более ранняя инструкция сможет прочитать оттуда свой операнд..
Но опасность WAW может ограничить способность ЦП запускать две инструкции одновременно . Это будет актуально только при написании регистра, который вы еще не прочитали. add r1, r1, r2
приходится ждать, пока r1
будет готов, прежде чем он сможет начать выполнение, потому что это ввод.
Например, если бы у вас был этот код, мы могли бы на самом деле увидеть отрицательную производительностьЭффект от записи одного и того же выходного регистра в 2-х инструкциях, которые могут выполняться в одном и том же цикле. Я не знаю, как Cortex-A53 или любой другой ARM по порядку справляется с этим, но другой процессор с двумя выпусками по порядку (Intel P5 Pentiumс 1993 г.) не объединяет инструкции, записывающие в один и тот же регистр ( Руководство xarch uarch Agner Fog для x86 ). 2-й должен ждать цикл перед запуском (но может быть pair с инструкцией после этого).
@ possible WAW hazard
adds r3, r1, r2 @ set flags, we don't care about the r3 output
add r3, r1, #5 @ now actually calculate an integer result we want
Если бы вы использовали другой фиктивный регистр вывода, они оба могли бы начаться в одном и том же тактовом цикле. (Или если бы выиспользуйте cmn r1, r2
(сравнение с отрицанием), вы могли бы установить флаги с r1 - (-r2)
без записи вывода вообще, что в соответствии с руководством такое же, как установка флагов с r1 + r2
.) Новозможно, есть какой-то случай, который вы можете придумать, который нельзя заменить инструкцией cmp
, cmn
, tst
(ANDS) или teq
(EORS).
Я ожидаю, что ARM не по порядку может переименовывать один и тот же регистр несколько раз за один и тот же цикл (процессоры OoO x86 могут это сделать), чтобы полностью избежать опасностей WAW.
Мне не известно о какой-либо микроархитектурной выгоде, когда некоторые регистры остаются "холодными".
На ЦП с переименованием регистров, обычно это делается с помощью физического файла регистров и даже безНедавно измененный архитектурный регистр (например, r3
) будет нуждаться в записи PRF для хранения значения любой инструкции, в которой он был последний раз написан, независимо от того, как давно это было.Таким образом, написание регистра всегда выделяет новый физический регистр и (в конце концов) освобождает физический регистр, содержащий старое значение.Независимо от того, было ли старое значение также только что записано, или оно долгое время имело это значение.
В семействе Intel P6 действительно использовался «файл регистров выхода на пенсию», в котором состояние выхода на пенсию хранится отдельно от «живых» значенийв нерабочем состоянииНо он сохранил эти значения в реальном регистре прямо в ROB с порождающим их uop (вместо ссылки на запись PRF), поэтому он не мог исчерпать физические регистры для переименования до того, как серверная часть была заполнена.См. http://blog.stuffedcow.net/2013/05/measuring-rob-capacity/ дополнительные интересные эксперименты с процессором x86, измеряющие пределы ROB и PRF для размера окна не по порядку для других процессоров x86, которые используют PRF.
Фактически из-за ограниченного чтенияпорты в файле регистров выхода на пенсию семейства P6 (PPro через Nehalem) могут фактически зависать при чтении слишком большого количества регистров, которые не были записаны в последнее время, в одной группе проблем.(См. Руководство по микроархам Agner Fog, зарегистрируйте прочитанные киоски.) Но я не думаю, что это типичная проблема для других уарчей, как и для любых вышедших из строя ядер ARM.Установите константы / инварианты циклов в регистрах вне циклов и свободно используйте их внутри.
Сноска 1 : это, как правило, верно для всех архитектур, но есть исключения.Единственный известный мне случай - довольно особый случай: на последних процессорах Intel x86 (в 64-битном режиме) mov eax, eax
(задержка 1 цикла) медленнее mov ecx, eax
(задержка 0 циклов) для усечения 64-битного регистрадо 32 бит, потому что mov-исключение работает только между разными регистрами.( Может ли MOV x86 действительно быть "бесплатным"? Почему я вообще не могу воспроизвести это? )