Пытаюсь получить addq, но продолжаю получать ответ - PullRequest
0 голосов
/ 22 октября 2019

Итак, я пытаюсь ознакомиться со сборкой и пытаюсь перепроектировать некоторый код. Моя проблема заключается в попытке декодировать addq, который, как я понимаю, выполняет Source + Destination = Destination.

Я использую допущения, что параметры x, y и z передаются в регистрах% rdi,% rsi и% rdx,Возвращаемое значение сохраняется в% rax.

long someFunc(long x, long y, long z){
1.  long temp=(x-z)*x;
2.  long temp2= (temp<<63)>>63;
3.  long temp3= (temp2 ^ x);
4.  long answer=y+temp3; 
5.  return answer;
}

Пока все, что выше строки 4, является именно тем, что я хочу. Тем не менее, строка 4 дает мне leaq (%rsi,%rdi), %rax, а не addq %rsi, %rax. Я не уверен, что я что-то не так делаю, но мне нужно какое-то понимание.

1 Ответ

0 голосов
/ 22 октября 2019

Эти инструкции не эквивалентны. Для LEA rax - это чистый вывод. Для вашего ожидаемого add это rax += rsi, поэтому компилятору сначала нужно будет mov %rdi, %rax. Это менее эффективно, поэтому оно этого не делает.

lea - это совершенно нормальный способ для компиляторов реализовать dst = src1 + src2, сохраняя инструкцию mov. В общем, не надоНе ожидайте, что операторы C будут компилироваться в инструкции, названные в их честь. Особенно маленькие сдвиги влево и добавление или умножение на 3, 5 или 9, потому что это главные цели для оптимизации с LEA. например, lea (%rsi, %rsi, 2), %rax реализует result = y*3. См. Использование LEA для значений, которые не являются адресами / указателями? для получения дополнительной информации. LEA также полезен, чтобы избежать разрушения любого из входов, если они оба необходимы позже.


Предполагая, что вы имели в виду t3, чтобы быть такой же переменной, как temp3, clang делает компилируйте, как вы ожидали, выполняя лучшую работу по распределению регистров, чтобы она могла использовать более короткую и более эффективную инструкцию add без каких-либо дополнительных mov инструкций вместо необходимости lea.

Clang выбирает лучшее распределение регистров, чем GCC , поэтому он может просто использовать add вместо необходимости lea для последней инструкции. ( Godbolt ). Это сохраняет размер кода (из-за режима индексированной адресации), и add имеет чуть лучшую пропускную способность, чем LEA, на большинстве процессоров, например 4 / тактовый вместо 2 / тактовый.

Clang также оптимизировал сдвиги вandl $1, %eax / negq %rax для создания результата 0 или -1 этого арифметического сдвига вправо = передача в битах. Он также оптимизирован до 32-битного размера операнда для первых нескольких шагов, потому что сдвиги отбрасывают все, кроме младшего бита temp1.

# side by side comparison, like the Godbolt diff pane
clang:                           |    gcc:
        movl    %edi, %eax              movq    %rdi, %rax
        subl    %edx, %eax              subq    %rdx, %rdi
        imull   %edi, %eax              imulq   %rax, %rdi  # temp1

        andl    $1, %eax                salq    $63, %rdi
        negq    %rax                    sarq    $63, %rdi   # temp2

        xorq    %rdi, %rax              xorq    %rax, %rdi  # temp3

        addq    %rsi, %rax              leaq    (%rdi,%rsi), %rax   # answer
        retq                            ret

Обратите внимание, что clang выбрал imul %edi, %eax (в RAX)но GCC решил размножиться в RDI. Это различие в распределении регистров, которое приводит к тому, что GCC требуется lea в конце вместо add.

Компиляторы иногда даже застревают с дополнительной инструкцией mov в конце небольшой функциикогда они делают неправильный выбор, как это, если последняя операция не была чем-то вроде сложения, которое можно сделать с lea в качестве неразрушающего операционного копирования. Это ошибки пропущенной оптимизации;вы можете сообщить о них в bugzilla GCC.


Другие пропущенные оптимизации

GCC и clang могли бы быть оптимизированы с помощью and вместо imul для установкимладший бит, только если оба входа нечетные.

Кроме того, поскольку имеет значение только младший бит вывода sub, сработал бы XOR (добавление без переноса) или даже добавление! (Нечетный + -even = нечетный. Четный + -even = четный. Нечетный + -odd = нечетный.) Это позволило бы lea вместо mov/sub в качестве первой инструкции.

        lea    (%rdi,%rsi), %eax
        and    %edi, %eax           # low bit matches (x-z)*x

        andl    $1, %eax            # keep only the low bit
        negq    %rax                # temp2

Позволяет сделатьТаблица истинности для младших битов x и z, чтобы увидеть, как это встряхивает, если мы хотим оптимизировать больше / иначе:

# truth table for low bit: input to shifts that broadcasts this to all bits
 x&1  |  z&1  | x-z = x^z | x*(x-z) = x & (x-z)
  0       0          0       0
  0       1          1       0
  1       0          1       1
  1       1          0       0
                            x & (~z) = BMI1 andn

Итак temp2 = (x^z) & x & 1 ? -1 : 0. Но также temp2 = -((x & ~z) & 1). Мы можем изменить это на -((x&1) & ~z), что позволяет нам начинать с not z и and $1, x параллельно, для лучшего ILP. Или, если z может быть готов в первую очередь, мы можем выполнить над ним операции и сократить критический путь с x -> answer, за счет z.

или с * 1086. * BMI1 andn инструкция, которая делает (~z) & x, мы можем сделать это в одной инструкции. (Плюс еще один для выделения младшего бита)

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

# hand-optimized
# long someFunc(long x, long y, long z)
someFunc:
        not    %edx                   # ~z
        and    $1, %edx
        and    %edi, %edx             # x&1 & ~z = low bit of temp1
        neg    %rdx                  # temp2 = 0 or -1

        xor    %rdi, %rdx            # temp3 = x or ~x

        lea    (%rsi, %rdx), %rax    # answer = y + temp3
        ret

Так что ILP все еще не существует, если z не будет готов до x и /или y. Используя дополнительную инструкцию mov, мы могли бы сделать x&1 параллельно с not z

Возможно, вы могли бы сделать что-то с test / setz или cmov, но IDK, если это побьетlea / and (temp1) + и / neg (temp2) + xor + add.

Я не смотрел на оптимизацию финального xor и add, но учтите, что temp3 в основном является условным НЕ x. Вы можете улучшить задержку за счет пропускной способности, рассчитав оба способа одновременно и выбрав их с помощью cmov. Возможно, с помощью идентичности дополнения 2, что -x - 1 = ~x. Может быть, улучшить ILP / латентность, выполнив x+y, а затем исправив это с помощью чего-то, что зависит от условий x и z? Поскольку мы не можем вычесть с помощью LEA, кажется, что лучше просто НЕ и ДОБАВИТЬ.

# return y + x  or   y + (~x) according to the condition on x and z
someFunc:
        lea    (%rsi, %rdi), %rax     # y + x

        andn   %edi, %edx, %ecx       # ecx = x & (~z)

        not    %rdi                   # ~x
        add    %rsi, %rdi             # y + (~x)

        test    $1, %cl
        cmovnz  %rdi, %rax            # select between y+x and y+~x
        retq

Это имеет больше ILP, но требует BMI1 andn, чтобы все еще быть только 6 (однократными) командами. Бродвелл и позже имеют однопользовательский CMOV;в более ранних версиях Intel это 2 мопа.

Другая функция может быть 5 моп с использованием BMI andn.

В этой версии первые 3 инструкции могут выполняться в первом цикле, предполагая, что x, y и z все готовы. Затем во 2-м цикле можно запустить ADD и TEST. В 3-м цикле CMOV может работать, принимая целочисленные входные значения от LEA, ADD и входные данные флага от TEST. Таким образом, общая задержка от x-> answer, y-> answer или z-> answer в этой версии составляет 3 цикла. (Предполагая однократный / однократный цикл). Прекрасно, если он находится на критическом пути, и не очень актуален, если он является частью независимой цепочки развертывания, и пропускная способность - все, что имеет значение.

против5 (andn) или 6 циклов (без) для предыдущей попытки. Или еще хуже для вывода компилятором, использующим imul вместо and (задержка 3 цикла только для этой инструкции).

...