Оновлення 2017-05-17. Я більше не працюю в компанії, де виникло це питання, і не маю доступу до Delphi XEx. Поки я там був, проблему було вирішено шляхом переходу на змішаний FPC + GCC (Pascal + C), з нетиповими елементами NEON для деяких процедур, де це змінило значення. (FPC + GCC настійно рекомендується ще й тому, що він дозволяє використовувати стандартні інструменти, зокрема Valgrind.) Якщо хтось може продемонструвати, з надійними прикладами, як вони насправді здатні виробляти оптимізований код ARM від Delphi XEx, я радий прийняти відповідь .
Компілятори Delphi Embarcadero використовують вихідний LLVM для створення нативного ARM-коду для пристроїв Android. У мене є велика кількість коду Pascal, який мені потрібно зібрати в додатки для Android, і я хотів би знати, як зробити Delphi генерувати більш ефективний код. Зараз я навіть не говорю про розширені функції, такі як автоматична оптимізація SIMD, просто про створення розумного коду. Напевно, повинен бути спосіб передати параметри стороні LLVM чи якимось чином вплинути на результат? Зазвичай будь-який компілятор матиме багато варіантів впливу на компіляцію та оптимізацію коду, але цілі ARM Delphi здаються просто "оптимізацією вмикання / виключення", і все.
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, і вони майже, але не зовсім такі, як версії для параметра 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). (Показана розборка не схожа на те, що вона читає бігендіальне значення, вона просто читає трохи ендіанське значення один байт за один раз.)