Подтвердить что ты не робот

Стоимость push против mov (стек против близкой памяти) и накладные расходы на вызовы функций

Вопрос:

Доступ к стеку достигает той же скорости, что и доступ к памяти?

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

Итак, в частности: push ax с той же скоростью, что и mov [bx], ax? Точно так же pop ax с той же скоростью, что и mov ax, [bx]? (предположим, что bx имеет место в памяти near.)

Мотивация для вопроса:

В C часто встречается препятствие тривиальным функциям, которые принимают параметры.

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

Но если предположить, что кто-то знает ответ на заголовок вопроса, должно быть возможно количественно накладные расходы, которые функция использует для настройки (push/pop/preserve context и т.д.) в терминах эквивалентного количества прямой памяти доступ. Отсюда заголовок вопроса.


( Изменить: Уточнение: near, приведенное выше, в отличие от far в сегментированной модели памяти 16 -битная архитектура x86.)
4b9b3361

Ответ 1

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

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

Кроме этого, я ожидаю, что последствия использования выбора "local var vs global var" зависят от шаблонов использования памяти. Если в ЦП имеется кеш памяти, стек, вероятно, будет в этом кеше, если вы не выделите и не освободите большие массивы или структуры на нем или не получите глубокие вызовы функций или глубокую рекурсию, что приведет к провалу кеша. Если глобальная переменная интереса доступна часто или если ее соседи получают доступ часто, я бы ожидал, что эта переменная будет находиться в кеше большую часть времени. Опять же, если вы получаете доступ к большим промежуткам памяти, которые не могут вписаться в кеш, у вас будут недостатки в кэше и, возможно, снижение производительности (возможно, потому что может быть или не быть лучшим, безопасным для кэша способом делать то, что вы хочу сделать).

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

Еще одним фактором является длина и декодирование команд. Инструкции по доступу к местоположению на стеке (относительно указателя стека) могут быть короче инструкций для доступа к произвольной ячейке памяти по заданному адресу. Более короткие инструкции могут быть декодированы и выполняться быстрее.

Я бы сказал, что нет окончательного ответа для всех случаев, потому что производительность зависит от:

  • ваше оборудование
  • ваш компилятор
  • ваша программа и ее шаблоны доступа к памяти

Ответ 2

Для часового цикла любопытно...

Для тех, кто хочет видеть конкретные тактовые циклы, таблицы команд/латентности для множества современных процессоров x86 и x86-64 доступный здесь (спасибо hirschhornsalz за указание на них).

Затем вы получите на чипе Pentium 4:

  • push ax и mov [bx], ax (красные боксы) практически идентичны по эффективности при одинаковых задержках и пропускной способности.
  • pop ax и mov ax, [bx] (blue boxed) одинаково эффективны с одинаковой пропускной способностью, несмотря на то, что mov ax, [bx] имеет удвоенную задержку pop ax

Pentium 4 Instruction Timing Table

Что касается последующего вопроса в комментариях (3-й комментарий):

  • косвенная адресация (т.е. mov [bx], ax) существенно не отличается от прямой адресации (т.е. mov [loc], ax), где loc представляет собой переменную, имеющую непосредственное значение, например. loc equ 0xfffd.

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

(Замечание: на самом деле даже еще в 8086 году с 1978 года использование стека было не менее эффективным, чем соответствующее mov в память, как видно из эти старые таблицы синхронизации команд 8086.)


Общие сведения о задержке и пропускной способности

Для понимания временных таблиц для современных процессоров может потребоваться немного больше. Они должны помочь: