Страницы

Поиск по вопросам

пятница, 13 декабря 2019 г.

выхлоп асма linux gcc с -O2 и без

#c #ассемблер


есть функция

int foo(int num) {
if(num)
    return 1;
else
    return 3;
}


мне понятен выхлоп без оптимизаций:

foo(int):
  pushq %rbp
  movq %rsp, %rbp
  movl %edi, -4(%rbp)
  cmpl $0, -4(%rbp)
  je .L2
  movl $1, %eax
  jmp .L3
.L2:
  movl $3, %eax
.L3:
  popq %rbp
  ret


но совершенно не понятно что происходит с O2:

foo(int):
  cmpl $1, %edi
  sbbl %eax, %eax
  andl $2, %eax
  addl $1, %eax
  ret


зачем вообще использовать SuBtract with Borrow...
к тому же если заменить возвращаемое значение return 3 на return 2, то вообще как-то
странно все отрабатывает

 foo(int):
  xorl %eax, %eax
  testl %edi, %edi
  sete %al
  addl $1, %eax
  ret


проясните немного что куда кладется при оптимизациях... а то что-то не могу въехать никак
    


Ответы

Ответ 1



Компилятор не обязан генерировать понятный и/или простой для понимания код. С другой стороны если провести все вычисления на бумажке для всех входов и выходов, то логика работы функции будет точно такая же: foo(int): cmpl $1, %edi # устанавливает CF, если %edi-1<0 т.е. если %edi==0 sbbl %eax, %eax # %eax = CF ? 0xFFFFFFFF : 0 andl $2, %eax # %eax &= 2 т.е. в зависимости от CF: %eax=={2|0} addl $1, %eax # %eax += 1 т.е. %eax=={3|1} ret Во втором случае всё проще, можно переписать в примерном псевдокоде Си: foo(int): xorl %eax, %eax # int rv=0; testl %edi, %edi # if(num==0) sete %al # rv = 1; addl $1, %eax # rv++; ret # return rv; Идея этих оптимизаций в том чтобы избавиться от команд условного перехода, которые на современных (i586+) ЦП, если блок предсказаний не угадывает, вызывают сброс конвейера, а следовательно значительно замедляют вычисления.

Ответ 2



Если вы поэкспериментируете с разными константами в качестве возвращаемых значений в вашей функции int foo(int num) { if(num) return A; else return B; } то можно заметить, что в общем случае в режиме -O2 компилятор в качестве оптимальной стратегии вычисления результата выбирает следующий подход int foo(int edi) { int eax = edi ? 0 : 0xFFFFFFFF; eax &= B - A; // `B - A` - константа return eax + A; } Комбинация cmpl $1, %edi sbbl %eax, %eax это ни что иное, как эффективный способ вычислить значение оператора ?:. А дальнейший код тривиально соответствует вышенаписанному. В случае же, когда возвращаемые константы отличаются не более чем на 1, компилятор выбирает другой подход - через sete.

Комментариев нет:

Отправить комментарий