Разрешен ли вызов функции оптимизации сборки, удаление констант из функции? - PullRequest
0 голосов
/ 19 октября 2018

У меня есть рекурсивная функция C

foo (int numFruits) {
   ....
   // recurse at some point
  } 

внутри главной функции.

Соответствующая сборка будет выглядеть следующим образом:

.pos 0x500
main:
   %r10  // contains numFruits
   call foo
   halt

.pos 0x4000
foo: // recursive
  irmovq $8, %r13 // load value 8 into %r13
  ...

Внутри foo, яЯ использую константное значение для размера четырехугольника длиной 8 байт.(значение 8 отсутствует в коде C, но я использую это значение для преобразования длины массива в соответствующий адрес и т. д.)

Если я загружаю это значение каждый раз, когда foo вызывается рекурсивно, ядумаю, что это пустая трата циклов.Мне было интересно, может ли компилятор оптимизировать его так, чтобы константы загружались перед вызовом foo в main?

Пример: загрузка значения 8 в r13 один раз перед вызовом foo, чтобы его не приходилось загружать каждый раз.(при условии, что r13 восстанавливается в исходное состояние перед загрузкой значения 8, после нажатия кнопки halt)

Если бы я сохранил значение 8 в r13 до main, это все равно сохраняло бы дух foo (int numFruits)или мое изменение эквивалентно foo (int numFruits, int quadSize)?

Спасибо

1 Ответ

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

Это эквивалентно foo(int numFruits, long quadSize).Ну, может быть int quadSize, если ваш y86 ABI имеет 64-битный int.Все обычные x86-64 ABI имеют 32-битный int, а Windows x64 даже имеет 32-битный long.

Вы также пометили этот x86.x86-64 может переместить 8 в 64-битный регистр с помощью 5-байтовой инструкции, например mov $8, %r13d: 1 байт кода операции + imm32.(На самом деле 6 байтов, включая префикс REX).Вам нужно mov r64, imm64 только для констант, которые не помещаются в 32-битные значения с нулевым или расширенным знаком.Запись 32-битного регистра начинается с нуля до полного 64-битного регистра.Вы можете даже настроить постоянную настройку игры в гольф за счет скорости.Как push $imm8 / pop %r13 в 3 байта (фактически 4 для префикса REX).При оптимизации размера кода вы хотите избежать r8..r15.https://codegolf.stackexchange.com/questions/132981/tips-for-golfing-in-x86-x64-machine-code/132985#132985.

Я понятия не имею, имеет ли y86 эффективные кодировки машинного кода для малых констант.

Не существует каких-либо физических процессоров y86, о которых я знаю.Есть эмуляторы, но я не уверен, есть ли вообще какие-либо виртуальные конструкции (например, verilog) аппаратного обеспечения y86, которые можно было бы смоделировать в симуляторе с точностью до цикла.

Так что любой разговор о "сохранении циклов"натяжка для y86. Реальные x86-64 процессоры являются конвейерными суперскалярными с неупорядоченным выполнением и часто не являются узкими местами при выборке кода.Особенно в современных процессорах с кешем UOP.В зависимости от цикла, дополнительные инструкции mov-немедленного из критического пути могут не замедлять работу. https://agner.org/optimize/, и видеть ссылки на производительность в x86 wiki .


Но да, вам, как правило, следует поднимать постоянную настройку из циклов.

Если ваш "цикл" является рекурсивным, вы не можете легко оптимизировать его в обычный цикл без дорогостоящего call /ret, вы, безусловно, можете сделать функцию-обертку для публичного использования, и она может превратиться в частную функцию, которая эффективно использует пользовательское соглашение о вызовах (которое предполагает, что %r13 = 8).

.globl foo
foo:
    irmovq  $8, %r13

 # .p2align 4    # optional 16-byte alignment for the recursion entry point
 # fall through

.Lprivate_foo:
   # only reachable with r13=8
 # blah blah using r13=8
    call .Lprivate_foo
 # blah blah still assuming r13=8
    call .Lprivate_foo
 # more stuff
    ret                      # the final return 

Ничегоеще можно позвонить private_foo;это локальный ярлык (.Lxxx), видимый только из этого источника.Таким образом, тело .Lprivate_foo может предполагать, что R13 = 8.

Если r13 является регистром, сохраняющим вызов, в вашем соглашении о вызовах y86 (как это происходит в x86-64 System V), тогда либо выберитерегистр с отложенным вызовом, такой как r11, или с функцией общедоступной оболочки call private_foo, чтобы он мог восстановить r13 вызывающего перед возвратом.Использование регистра, который функциям обычно разрешено блокировать, приводит к тому, что это почти нулевые дополнительные издержки вместо введения дополнительного уровня call / ret.

Но это работает, только если вы не вызываете какие-либо другие неизвестные функции изнутриваша рекурсивная функция, в противном случае вы должны предположить, что они перекрыли R11.

Оптимизация рекурсии в цикле имеет большое преимущество, и компиляторы делают это всякий раз, когда это возможно.(В двойной рекурсивной функции, такой как обход дерева, они часто превращают 2-й рекурсивный вызов в ветвь цикла, но все же фактически выполняют рекурсию для нехвостовой рекурсии.)


Если выпросто используя 8 в качестве коэффициента масштабирования, я беспокоюсь, что вы используете умножение.Гораздо эффективнее использовать сдвиг на 3. Или (поскольку вы пометили этот x86, а также y86), возможно, используйте режим адресации с масштабированным индексом.Но если это для увеличения указателя, то реальный x86 будет использовать немедленное добавление.Как и add $8, %rsi, используется кодировка add r/m64, imm8 , в которой для константы используется только 1 байт (знак расширен до 64 бит).

Но эквивалент x86 будет векторной константой SIMDили константы с плавающей запятой, потому что их нет.И в этом случае да, вы хотите установить константу в регистре вне цикла.

...