Как умножить регистр на 37, используя только две последовательные инструкции в x86?

Скажем,%edi содержит x, и я хочу получить 37*x, используя только 2 последовательные инструкции по обучению, как бы мне поступить?

Например, чтобы получить 45x вы бы сделали

leal (%edi, %edi, 8), %edi   
leal (%edi, %edi, 4), %eax (to be returned)

Я не могу на всю жизнь выяснить, какие числа поставить вместо 8 и 4, чтобы результат (%eax) был 37x

1 ответ

Решение

В -O3 , GCC будет излучать (проводник компилятора Godbolt):

int mul37(int a)  { return a*37; }

    leal    (%rdi,%rdi,8), %eax      # eax = a * 9
    leal    (%rdi,%rax,4), %eax      # eax = a + 4*(a*9)
    ret

Это с помощью 37 = 9*4 + 1, не уничтожая оригинал a значение с первым lea так что можно использовать как во 2-м.

Вы в хорошей компании, не заметив этого, хотя: недавний clang (3.8 и новее) будет обычно использовать 2 lea инструкции вместо imul (например, для *15), но он пропускает этот и использует:

    imull   $37, %edi, %eax
    ret

Это делает *21 с тем же шаблоном, который использует gcc, как 5*4 + 1, (clang3.6 и ранее всегда используется imul если не было альтернативы одной инструкции shl или же lea)

ICC и MSVC также используют imul, но им не нравится использовать 2 lea инструкции, поэтому imul "намеренно" там.

См. Ссылку на Godbolt для различных множителей с gcc7.2 против clang5.0. Интересно попробовать gcc -m32 -mtune=pentium или даже pentium3 чтобы увидеть, сколько еще инструкций собирался использовать gcc тогда. Хотя P2/P3 имеет 4-тактную задержку для imul r, r, i так что это довольно безумно Pentium имеет 9 цикла imul и нет ООО, чтобы скрыть задержку, поэтому имеет смысл постараться избежать этого.

mtune=silvermont вероятно, стоит только заменить 32-битный imul с одной инструкцией, потому что она имеет умножение задержки на 3 цикла / 1c, но декодирование часто является узким местом (согласно Agner Fog, http://agner.org/optimize/). Вы могли бы даже рассмотреть imul $64, %edi, %eax (или другие полномочия 2) вместо mov / shl потому что imul-немедленное является копией и умножением.


Как ни странно, gcc пропускает * 45 случай, и использует imul в то время как лязг использует 2 lea s. Похоже, пришло время подать несколько отчетов об ошибках при пропущенной оптимизации. Если 2 LEA лучше, чем 1 IMUL, их следует использовать везде, где это возможно.

Более старый Clang (3.7 и старше) использует imul если только один lea сделает свое дело. Я не просматривал журнал изменений, чтобы увидеть, сделали ли они тесты, чтобы решить, предпочитают ли задержки пропускную способность.


Связанный: Использование LEA на значениях, которые не являются адресами / указателями? канонический ответ о том, почему LEA использует синтаксис операндов памяти и машинное кодирование, даже если это инструкция shift+add (и выполняется в ALU, а не AGU в большинстве современных микроархитектур).

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