Обновление 2017-05-17. Я больше не работаю в компании, где возник этот вопрос, и у меня нет доступа к Delphi XEx. В то время как я был там, проблема была решена путем миграции на смешанный FPC + GCC (Pascal + C), с NEON intrinsics для некоторых подпрограмм, где это имело значение. (FPC + GCC настоятельно рекомендуется также потому, что он позволяет использовать стандартные инструменты, в частности Valgrind.) Если кто-то может продемонстрировать, с достоверными примерами, как они на самом деле способны создавать оптимизированный код ARM из Delphi XEx, я рад принять ответ.
Компиляторы Embarcadero Delphi используют LLVM-сервер для создания собственного кода ARM для устройств Android. У меня есть большое количество кода Паскаля, которые мне нужно скомпилировать в приложениях Android, и я хотел бы знать, как заставить Delphi генерировать более эффективный код. Прямо сейчас, я даже не говорю о таких расширенных функциях, как автоматическая оптимизация SIMD, а также о создании разумного кода. Разумеется, должен быть способ передать параметры стороне LLVM или каким-то образом повлиять на результат? Как правило, любой компилятор будет иметь множество вариантов, влияющих на компиляцию и оптимизацию кода, но цели Delphi ARM кажутся просто "оптимизацией вкл/выкл" и что она.
Предполагается, что LLVM способен создавать разумно жесткий и разумный код, но, похоже, Delphi использует свои возможности странным образом. Delphi хочет использовать стек очень сильно, и он обычно использует только регистры процессора r0-r3 в качестве временных переменных. Возможно, самым сумасшедшим из всех, кажется, загружаются нормальные 32-битные целые числа в виде четырех 1-байтовых операций загрузки. Как заставить Delphi создавать улучшенный код ARM, и без байт-байтовой суеты он делает для Android?
Сначала я думал, что байтовая байтовая загрузка предназначена для замены байтового байта от big-endian, но это не так, на самом деле это просто загрузка 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, и они почти точно не совпадают с версией параметров Android var. Изменить: чтобы уточнить, побайтовая загрузка загружается только на 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