Ассамблея: Почему мы беспокоимся о регистрах? - PullRequest
15 голосов
/ 02 марта 2010

У меня есть основной вопрос по сборке.

Почему мы занимаемся арифметическими операциями только с регистрами, если они могут работать и с памятью?

Например, оба следующих фактора (по существу) приводят к тому, что одно и то же значение рассчитывается как ответ:

Фрагмент 1

.data
    var dd 00000400h

.code

    Start:
        add var,0000000Bh
        mov eax,var
        ;breakpoint: var = 00000B04
    End Start


Фрагмент 2

.code

    Start:
        mov eax,00000400h
        add eax,0000000bh
        ;breakpoint: eax = 0000040B
    End Start



Из того, что я вижу, большинство текстов и учебных пособий выполняют арифметические операции в основном над регистрами. Просто с регистрами работать быстрее?

Редактировать: Это было быстро:)

Было дано несколько великолепных ответов; лучший ответ был выбран на основе первого хорошего ответа.

Ответы [ 9 ]

25 голосов
/ 02 марта 2010

Если вы посмотрите на компьютерные архитектуры, вы обнаружите ряд уровней памяти. Те, которые находятся близко к ЦП, являются быстрыми, дорогими (на бит) и поэтому маленькими, в то время как на другом конце у вас большие, медленные и дешевые устройства памяти. В современном компьютере это обычно что-то вроде:

 CPU registers (slightly complicated, but in the order of 1KB per a core - there
                are different types of registers. You might have 16 64 bit
                general purpose registers plus a bunch of registers for special
                purposes)
 L1 cache (64KB per core)
 L2 cache (256KB per core)
 L3 cache (8MB)
 Main memory (8GB)
 HDD (1TB)
 The internet (big)

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

Существует резкое (часто на несколько порядков) уменьшение пропускной способности и увеличение задержка на каждом шаге от ЦП. Например, жесткий диск может считываться со скоростью 100 МБ / с с задержкой 5 мс (эти цифры могут быть не совсем точными), тогда как ваша основная память может считывать со скоростью 6,4 ГБ / с с задержкой 9 нс (шесть порядков величина!). Задержка является очень важным фактором, так как вы не хотите заставлять процессор ждать дольше, чем нужно (это особенно верно для архитектур с глубокими конвейерами, но это обсуждение для другого дня).

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

Еще одно преимущество наличия регистров (и только небольшого числа регистров) состоит в том, что они позволяют иметь более короткие инструкции . Если у вас есть инструкции, содержащие два (или более) 64-битных адреса, у вас будут длинные инструкции!

9 голосов
/ 02 марта 2010

Регистры намного быстрее, а операции, которые вы можете выполнять непосредственно в памяти, гораздо более ограничены.

3 голосов
/ 02 марта 2010

Доступ к регистрам в порядке быстрее, чем к оперативной памяти, поскольку вам не требуется доступ к «медленной» шине памяти!

2 голосов
/ 10 августа 2017

x86, как и почти любой другой «нормальный» процессор, для которого вы, возможно, изучите сборку, является «регистрирующей машиной» . Существуют и другие способы проектирования чего-либо, что вы можете программировать (например, машина Тьюринга, которая движется по логической «ленте» в памяти), но регистрационные машины оказались в основном единственным способом достижения высокой производительности.

Поскольку x86 был разработан для использования регистров, вы не можете полностью избежать их, даже если бы вы хотели и не заботились о производительности.

Текущие процессоры x86 могут читать / записывать гораздо больше регистров за такт, чем ячейки памяти.

Например, Intel Skylake может выполнять две загрузки и одно хранилище из / в свой 32-килобайтный ассоциативный L1D-кэш 32 кбайт за цикл (в лучшем случае), но может читать более 10 регистров за такт и записывать 3 или 4 (плюс EFLAGS) .

Создание кэша L1D с таким количеством портов чтения / записи, как файл регистра , будет непомерно дорогим (по количеству транзисторов / площади и потребляемой мощности), особенно если вы хотите сохранить его таким большим, как он является. Вероятно, просто физически невозможно создать что-то, что может использовать память, как x86 использует регистры с той же производительностью.

Кроме того, запись регистра и последующее его чтение имеют практически нулевую задержку, поскольку ЦП обнаруживает это и перенаправляет результат непосредственно с выхода одного исполнительного устройства на вход другого, минуя этап обратной записи. (См. https://en.wikipedia.org/wiki/Classic_RISC_pipeline#Solution_A._Bypassing).

Эти соединения для пересылки результатов между исполнительными блоками называются «обходной сетью» или «сетью пересылки», и для ЦП это гораздо проще сделать для конструкции регистра, чем если бы все приходилось идти в память и обратно. ЦПУ должен только проверять номер регистра из 3–5 бит вместо 32-битного или 64-битного адреса, чтобы обнаружить случаи, когда вывод одной инструкции необходим сразу как ввод для другой операции. (И эти регистрационные номера жестко запрограммированы в машинном коде, поэтому они доступны сразу.)

Как уже упоминалось, 3 или 4 бита для адресации регистра делают формат машинного кода намного более компактным, чем если бы каждая инструкция имела абсолютные адреса.


См. Также https://en.wikipedia.org/wiki/Memory_hierarchy: Вы можете думать о регистрах как о небольшом быстром фиксированном размере памяти, отделенной от основной памяти, где поддерживается только прямая абсолютная адресация. (Вы не можете «проиндексировать» регистр: если в одном регистре задано целое число N, вы не можете получить содержимое N-го регистра с одним insn.)

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

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

См. Почему Мулсс занимает всего 3 цикла в Haswell, в отличие от таблиц инструкций Агнера? для объяснения переименования регистров и конкретного примера (более поздние правки к вопросу / более поздним частям моего ответа) показывает ускорение от развертывания с несколькими аккумуляторами, чтобы скрыть задержку FMA, даже если он многократно использует один и тот же архитектурный регистр).


Буфер хранилища с пересылкой хранилища в основном дает вам «переименование памяти». Сохранение / перезагрузка в ячейку памяти не зависит от предыдущих хранилищ и загружается в эту ячейку из этого ядра.

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

Сохранение / перезагрузка секунд может выполняться, даже если первое хранилище все еще ожидает ввода. (Я проверял это на Skylake, но IDK, если я когда-нибудь опубликовал результаты в ответе.)

1 голос
/ 02 марта 2010

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

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

1 голос
/ 02 марта 2010

Мы используем регистры, потому что они быстрые. Обычно они работают на скорости процессора.
Регистры и кэш-память ЦП выполнены с использованием различных технологий / тканей и
они дорогие. С другой стороны, ОЗУ дешево и в 100 раз медленнее.

0 голосов
/ 02 марта 2010

Просто набор инструкций не позволит вам выполнять такие сложные операции:

add [0x40001234],[0x40002234]

Вы должны пройти через регистры.

0 голосов
/ 02 марта 2010

Да - также вы можете легко вставлять / извлекать регистры для вызова процедур, обработки прерываний и т. Д.

0 голосов
/ 02 марта 2010

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

...