Инструкции MUL/DIV против MOV & SHL/SHR (Pentium Pro)

Почему вы хотите использовать:

MOV EAX, 22 
SHL EAX, 2

... при умножении на 4 вместо использования MUL инструкция?
Я понимаю, что это также может быть сделано с SHR вместо DIV также.

Каковы преимущества этого?
Также вы можете сделать это с нечетными числами или это могут быть только четные числа?

2 ответа

Решение

С использованием SHL/SHR Инструкция, вообще говоря, намного быстрее, чем MUL/DIV,

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

Например: следующий код умножается на 5 без использования MUL инструкция:

mov num, 5
mov eax, num
mov ebx, num
shl eax, 2    ; MULs by 4
add eax, ebx  ; ADD the x1 to make = 5

Есть ряд кодовых идиом, которые быстрее, чем "константа MUL".

Современные процессоры x86 выполняют MUL за несколько часов, как минимум. Таким образом, любая кодовая последовательность, которая вычисляет произведение за 1-2 такта, будет превосходить MUL. Вы можете использовать быстрые инструкции (ADD, SHL, LEA, NEG) и тот факт, что процессор может выполнять некоторые из этих команд параллельно за один такт, чтобы заменить MUL. Возможно, это означает, что вы можете выполнить 4 из этих инструкций во многих комбинациях за 2 такта, если избегаете некоторых зависимостей данных.

Инструкция LEA особенно интересна, потому что она может умножаться на несколько небольших констант (1,2,3,4,5,8,9), а также перемещать продукт в другой регистр, что является одним из простых способов разорвать зависимости данных. Это позволяет вам вычислять субпродукт, не разрушая исходный операнд.

Некоторые примеры:

Умножьте EAX на 5, переместите продукт в ESI:

   LEA ESI, [EAX+4*EAX]    ; this takes 1 clock

Умножьте EAX на 18:

   LEA  EAX, [EAX + 8*EAX]
   SHL  EAX, 1

Умножьте EAX на 7, переместите результат в EBX:

   LEA  EBX, [8*EAX]
   SUB  EBX, EAX

Умножьте EAX на 28:

   LEA  EBX, [8*EAX]
   LEA  ECX, [EAX+4*EAX]  ; this and previous should be executed in parallel
   LEA  EAX, [EBX+4*ECX]

Умножьте на 1020:

   LEA  ECX, [4*EAX]
   SHL  EAX, 10         ; this and previous instruction should be executed in parallel
   SUB  EAX, ECX

Умножить на 35

   LEA  ECX, [EAX+8*EAX]
   NEG  EAX             ; = -EAX
   LEA  EAX, [EAX+ECX*4]

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

Примечательно, сколько умноженных на константы может быть получено таким способом. Вы можете подумать, что это полезно только для действительно маленьких констант, но, как вы можете видеть из приведенного выше примера 1020, вы также можете получить несколько удивительно средних. Это оказывается очень удобным при индексации массивов структур, потому что вы должны умножить индекс на размер структуры. Часто при индексации такого массива вы хотите вычислить адрес элемента и получить значение; в этом случае вы можете объединить окончательную инструкцию LEA с инструкцией MOV, чего нельзя сделать с настоящим MUL. Это дает вам дополнительный тактовый цикл (ы) для выполнения MUL по этому типу идиомы.

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

Другие вопросы по тегам