Обновление 2017-05-17. Я больше не работаю в компании, где возник этот вопрос, и не имею доступа к Delphi XEx. Пока я был там, проблема была решена путем перехода на смешанный FPC + GCC (Pascal + C) с встроенными NEON для некоторых подпрограмм, где это имело значение. (FPC + GCC настоятельно рекомендуется также потому, что он позволяет использовать стандартные инструменты, в частности Valgrind.) Если кто-то может продемонстрировать на достоверных примерах, как он на самом деле способен производить оптимизированный код ARM из Delphi XEx, я рад принять ответ ,
Компиляторы Embarcadero Delphi используют бэкэнд LLVM для создания собственного кода ARM для устройств Android. У меня есть большое количество кода на Pascal, которое мне нужно скомпилировать в приложения для Android, и я хотел бы знать, как заставить Delphi генерировать более эффективный код. Прямо сейчас я даже не говорю о расширенных функциях, таких как автоматическая оптимизация SIMD, просто о создании разумного кода. Наверняка должен быть способ передачи параметров на сторону LLVM или как-то повлиять на результат? Обычно у любого компилятора будет много опций, влияющих на компиляцию и оптимизацию кода, но цели Delphi ARM кажутся просто «оптимизацией вкл / выкл» и все.
Предполагается, что LLVM способен производить достаточно жесткий и разумный код, но кажется, что Delphi использует свои возможности странным образом. Delphi хочет использовать стек очень интенсивно и обычно использует только регистры процессора r0-r3 в качестве временных переменных. Пожалуй, самым сумасшедшим из всех, кажется, является загрузка обычных 32-битных целых чисел в виде четырех 1-байтовых операций загрузки. Как заставить Delphi создавать лучший ARM-код, и без побайтных хлопот, которые он создает для Android?
Сначала я думал, что побайтная загрузка предназначена для замены порядка байтов в старшей последовательности, но это не так, это просто загрузка 32-битного числа с 4 однобайтовыми загрузками. * Это может быть загрузка полные 32 бита без выравнивания по размеру памяти. (ДОЛЖЕН ли он избегать этого, это другая вещь, которая намекала бы на то, что все это - ошибка компилятора) *
Давайте посмотрим на эту простую функцию:
function ReadInteger(APInteger : PInteger) : Integer;
begin
Result := APInteger^;
end;
Даже при включенной оптимизации Delphi XE7 с пакетом обновлений 1, а также XE6 создают следующий код сборки ARM для этой функции:
Disassembly of section .text._ZN16Uarmcodetestform11ReadIntegerEPi:
00000000 <_ZN16Uarmcodetestform11ReadIntegerEPi>:
0: b580 push {r7, lr}
2: 466f mov r7, sp
4: b083 sub sp, #12
6: 9002 str r0, [sp, #8]
8: 78c1 ldrb r1, [r0, #3]
a: 7882 ldrb r2, [r0, #2]
c: ea42 2101 orr.w r1, r2, r1, lsl #8
10: 7842 ldrb r2, [r0, #1]
12: 7803 ldrb r3, [r0, #0]
14: ea43 2202 orr.w r2, r3, r2, lsl #8
18: ea42 4101 orr.w r1, r2, r1, lsl #16
1c: 9101 str r1, [sp, #4]
1e: 9000 str r0, [sp, #0]
20: 4608 mov r0, r1
22: b003 add sp, #12
24: bd80 pop {r7, pc}
Просто посчитайте количество инструкций и обращений к памяти, необходимых для этого Delphi. И построение 32-битного целого числа из 4 однобайтовых загрузок ... Если я немного изменю функцию и использую параметр var вместо указателя, это будет немного менее запутанным:
Disassembly of section .text._ZN16Uarmcodetestform14ReadIntegerVarERi:
00000000 <_ZN16Uarmcodetestform14ReadIntegerVarERi>:
0: b580 push {r7, lr}
2: 466f mov r7, sp
4: b083 sub sp, #12
6: 9002 str r0, [sp, #8]
8: 6801 ldr r1, [r0, #0]
a: 9101 str r1, [sp, #4]
c: 9000 str r0, [sp, #0]
e: 4608 mov r0, r1
10: b003 add sp, #12
12: bd80 pop {r7, pc}
Я не буду включать здесь разборку, но для iOS Delphi создает идентичный код для версий указателя и параметров var, и они почти, но не совсем совпадают с версией параметра var Android. Изменить: чтобы уточнить, побайтовая загрузка только на Android. И только на Android версии указателя и параметра var отличаются друг от друга. На iOS обе версии генерируют абсолютно одинаковый код.
Для сравнения, вот что FPC 2.7.1 (версия магистрали SVN от марта 2014 г.) думает о функции с уровнем оптимизации -O2. Версии указателя и параметра var одинаковы.
Disassembly of section .text.n_p$armcodetest_$$_readinteger$pinteger$$longint:
00000000 <P$ARMCODETEST_$$_READINTEGER$PINTEGER$$LONGINT>:
0: 6800 ldr r0, [r0, #0]
2: 46f7 mov pc, lr
Я также протестировал эквивалентную функцию C с помощью компилятора C, который поставляется с Android NDK.
int ReadInteger(int *APInteger)
{
return *APInteger;
}
И это компилируется по существу в то же самое, что сделал FPC:
Disassembly of section .text._Z11ReadIntegerPi:
00000000 <_Z11ReadIntegerPi>:
0: 6800 ldr r0, [r0, #0]
2: 4770 bx lr
armeabi-v7a
вместо armeabi
(не уверен, есть ли такие опции в этом компиляторе), так как должны поддерживаться невыровненные загрузки, начиная с ARMv6 (хотя armeabi
предполагается, что ARMv5). (Показанная дизассемблирование не похоже на то, что она читает бигендовское значение, она просто читает небольшое порядковое значение по одному байту за раз.)