Изучая курс компиляторов, меня оставляют, задаваясь вопросом, почему использование регистрируется вообще. Часто имеет место, что вызывающая сторона или вызываемый должны сохранить значение регистра и затем восстановить его.
В некотором роде они всегда заканчивают тем, что использовали стек так или иначе. Создает дополнительную сложность при помощи регистров, действительно стоящих того?
Извините мое незнание.
Обновление: я знаю, что регистры быстрее, чем RAM и другие типы кэша. Мое основное беспокойство - то, что нужно "сохранить" значение, которое находится в регистре и "восстановлении" это к регистру впоследствии. В обоих случаях мы получаем доступ к некоторому кэшу. Разве не было бы лучше использовать кэш во-первых?
В иерархии скорости / задержки регистры являются самыми быстрыми (обычно задержка нулевого цикла), следующим идет кэш L1 (обычно задержка 1 или более), и затем он быстро идет вниз. Таким образом, в общем случае доступ к регистрам является «бесплатным», тогда как доступ к памяти всегда требует определенных затрат, даже если этот доступ кэшируется.
Сохранение и восстановление регистров обычно происходит только (а) в начале / конце вызова функции или переключения контекста, или (б) когда компилятор исчерпывает регистры для временных переменных и ему необходимо «пролить» один или несколько регистров. вернуться к памяти. В общем, хорошо оптимизированный код будет хранить большинство часто используемых («горячих») переменных в регистрах, по крайней мере, в самом внутреннем цикле (ах) функции.
Это может иметь огромное значение. Однажды я приказал компилятору PowerPC / Macintosh поместить правильные локальные переменные в регистры и в 2 раза ускорил выполнение основной задачи обработки приложения. Задача была по существу связана с процессором, но устранение доступа к памяти с помощью регистров дало ускорение в 2 раза. В других обстоятельствах ускорение может быть гораздо более значительным.
Это была листовая функция. Он не вызывал других функций.
Доступ к ОЗУ обычно НАМНОГО медленнее, чем доступ к регистру, как с точки зрения задержки, так и с точки зрения пропускной способности. Существуют процессоры с аппаратным стеком ограниченного размера - это позволяет помещать регистры в стек и возвращать их обратно - но они по-прежнему используют регистры непосредственно для вычислений. Работа с чистой стековой машиной (которой есть много академических примеров) тоже довольно сложна, что добавляет сложности.
Я бы сказал, что это не проблема с компиляторами, как с процессорами. Компиляторы должны работать с целевой архитектурой.
Вот что умалчивают другие ответы: это зависит от архитектуры ЦП на уровне реальной схемы. Машинные инструкции сводятся к получению данных откуда-то, изменению данных, загрузке или переходу к следующей инструкции.
Думайте о проблеме, как о плотнике, который строит или ремонтирует стул для вас. Его вопросы будут: «Где стул?» И «Что нужно сделать со стулом». Возможно, он сможет починить его у вас дома, или ему, возможно, придется отнести стул в свой магазин, чтобы поработать над ним. Любой способ будет работать, но зависит от того, насколько он подготовлен к работе за пределами определенного места. Это могло его замедлить или это могло быть его специальностью.
Теперь вернемся к процессору.
Независимо от того, насколько параллельным может быть ЦП, например, наличие нескольких сумматоров или конвейеров декодирования команд, эти схемы расположены в определенных местах на кристалле, и данные должны быть загружены в те места, где может выполняться операция. . Программа отвечает за перемещение данных в эти места и из них. В стековой машине он может предоставлять инструкции, которые изменяют данные напрямую, но может выполнять служебные функции в микрокоде. Сумматор работает одинаково независимо от того, пришли данные из стека или из кучи. Разница в модели программирования, доступной программисту. Регистры - это в основном определенное место для работы с данными.
Ну, похоже, ответ на этот вопрос также был в книге (современная реализация компилятора на java). В книге представлены 4 ответа:
Разница между машинами на основе стека и регистров нечеткая. Многие современные регистровые машины используют переименование регистров, чтобы скрыть стек, сбрасывая данные в фактический стек только тогда, когда у них заканчиваются внутренние регистры. Некоторые старые стековые машины делали нечто подобное, передавая несколько инструкций по конвейеру и визуально оптимизируя последовательность push-modify-pop для изменения на месте.
То, как современные процессоры используют причудливое параллельное выполнение инструкций, вероятно, не имеет большой разницы между стековыми и регистровыми машинами. Регистрирующие машины могут иметь небольшое преимущество, потому что компилятор может дать более точные подсказки о повторном использовании данных, но стековая машина Lisp стоимостью в миллиард долларов была бы очень быстрой, если бы Intel позаботилась о ее разработке.
Я думаю, вы спрашиваете, зачем использовать регистры, поскольку переменные в конечном итоге все равно попадают в стек.
Ответ таков: представьте регистры как кеш для верхних 5 или 6 (или любых других) элементов на вершине стека. Если доступ к верхней части стека осуществляется намного больше, чем к нижней (что верно для многих программ), то наличие этого кеша ускорит процесс.
Думаю, вы могли бы сказать, зачем вообще нужны регистры, видимые пользователем, вместо прозрачного кеша нескольких верхних битов. Я не уверен, но подозреваю, что предоставление компилятору информации о том, какие значения будут кэшироваться, позволяет лучше оптимизировать распределение памяти. В конце концов, если в стеке есть некоторая отсечка, после которой доступ к переменным будет намного дороже, вы должны настроить свои переменные для работы с этим.