LEA или ДОБАВИТЬ инструкцию? - PullRequest
43 голосов
/ 12 июня 2011

Когда я пишу почерк, я обычно выбираю форму

lea eax, [eax+4]

Над формой ..

add eax, 4

Я слышал, что lea - это команда "0 часов" (например, NOP), а add - нет. Однако, когда я смотрю на сборку, созданную компилятором, я часто вижу, что используется последняя форма вместо первой. Я достаточно умен, чтобы доверять компилятору, поэтому кто-нибудь может пролить свет на то, какой из них лучше? Какой из них быстрее? Почему компилятор выбирает последнюю форму поверх первой?

Ответы [ 5 ]

53 голосов
/ 13 июня 2011

Существенным отличием между LEA и ADD на процессорах x86 является исполнительный модуль, который фактически выполняет инструкцию. Современные процессоры x86 являются суперскалярными и имеют несколько исполнительных блоков, которые работают параллельно, а конвейер подает их несколько как циклический перебор. Дело в том, что LEA обрабатывается (одним из) блоком (ами), имеющим дело с адресацией (что происходит на ранней стадии в конвейере), в то время как ADD переходит к ALU (арифметическим / логическим единицам) и поздно в конвейере. Это означает, что суперскалярный процессор x86 может одновременно выполнять LEA и арифметическую / логическую инструкцию.

Тот факт, что LEA проходит через логику генерации адреса вместо арифметических единиц, также является причиной, по которой его раньше называли "нулевыми часами"; выполнение не занимает времени, потому что генерация адреса уже произошла к тому времени, когда он будет / выполнен.

Это не бесплатно , так как генерация адреса является шагом в конвейере выполнения, но у него нет накладных расходов на выполнение. И он не занимает слот в конвейере (ах) ALU.

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

15 голосов
/ 12 июня 2011

Я достаточно умен, чтобы доверять компилятору, поэтому кто-нибудь может пролить свет на то, какой из них лучше?

Да, немного. Во-первых, я беру это из следующего сообщения: https://groups.google.com/group/bsdnt-devel/msg/23a48bb18571b9a6

В этом сообщении разработчик оптимизирует некоторые сборки, которые я написал очень плохо, для безумно быстрой работы на процессорах Intel Core 2. В качестве фона для этого проекта используется библиотека bsd bignum, в которой я и несколько других разработчиков участвовали.

В этом случае оптимизируется только добавление двух массивов, которые выглядят следующим образом: uint64_t* x, uint64_t* y. Каждый «член» или член массива представляет часть bignum; основной процесс состоит в том, чтобы перебрать его, начиная с наименее значимой конечности, добавить пару и продолжить вверх, передавая перенос (любое переполнение) каждый раз. adc делает это для вас на процессоре (не возможно получить доступ к флагу переноса из C, я не думаю).

В этом фрагменте кода используется комбинация lea something, [something+1] и jrcxz, которые, по-видимому, более эффективны, чем пара jnz / add something, size, которую мы могли ранее использовать. Однако я не уверен, что это было обнаружено в результате простого тестирования различных инструкций. Вы должны спросить.

Однако в более позднем сообщении он измеряется на чипе AMD и не так хорошо работает.

Мне также дано понимать, что разные операции по-разному выполняются на разных процессорах. Я знаю, например, что проект GMP обнаруживает процессоры, использующие cpuid, и передает разные процедуры сборки на основе разных архитектур, например, core2, nehalem.

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

9 голосов
/ 12 июня 2011

LEA не быстрее, чем инструкция ADD, скорость выполнения такая же.

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

1 голос
/ 03 октября 2018

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

DST = DST <operation> SRC

LEA является своего рода инструкцией взлома, потому что это инструкция SINGLE в ISA x86, которая на самом деле является трехадресной:

DST = SRC1 <operation> SRC2

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

Компиляторы используют LEA, потому что это вмешательство позволяет им заменять несколько вторжений одной инструкцией вслучаи, когда содержимое регистров слагаемых выгодно сохранить без изменений.Обратите внимание, что во всех случаях, когда компилятор использует регистр LEA DST, отличается от регистра SRC или аргумент SRC использует логику вычисления сложных адресов.

Например, в сгенерированном коде практически невозможно найти такой вариант использования:

LEA EAX, [EAX   ] // equivalent of NOP
LEA EAX, [ECX   ] // equivalent of MOV EAX, ECX
LEA EAX, [EAX+12] // equivalent of ADD EAX, 12

, но распространены следующие варианты использования:

LEA EAX, [ECX      +12] // there is no single-instruction equivalent
LEA EAX, [ECX+EDX*4+12] // there is no single-instruction equivalent
LEA EDX, [ECX+EDX*4+12] // there is no single-instruction equivalent

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

LEA EAX, [EBP+12]
LEA EDX, [EBP+48]

Всего две инструкции!Но в случае отсутствия LEA код будет следующим

MOV EAX, EBP
MOV EDX, EBP
ADD EAX, 12
ADD EDX, 48

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

LEA EDX, [ECX+EDX*4+12] // there is no single-instruction equivalent

на ADD-код.

0 голосов
/ 04 октября 2015

Вы можете выполнить инструкцию lea в одном и том же тактовом цикле, как операция добавления, но если вы используете lea и сложите вместе, вы можете выполнить добавление трех операндов только в одном цикле!Если вы используете две операции добавления, которые могут выполняться только за 2 такта:

mov eax, [esp+4]   ; get a from stack
mov edx, [esp+8]   ; get b from stack
mov ecx, [esp+12]  ; get c from stack
lea eax, [eax+edx] ; add a and b in the adress decoding/fetch stage of the pipeline
add eax, ecx       ; Add c + eax in the execution stage of the pipeline
ret 12
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...