Как повлиять на генерацию кода Delphi XEx для целей Android / ARM?

257

Обновление 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
  • 3
    FWIW, Android не хочет ничего в формате с прямым порядком байтов (все текущие платформы Android имеют прямой порядок байтов), поэтому Android не виноват в этом (особенно если вы видели такое же поведение на сборках iOS).
  • 3
    Это только на Android. На iOS Delphi не генерирует такую загрузку байтов за байтом. В iOS версия указателя и версия параметра var в точности совпадают, но в Android версия указателя строит целое число из 4 байтов. Код Pascal, естественно, был точно таким же, поскольку я использовал один и тот же компилятор XE7 для Android и iOS, просто меняя цель. Я позаботился о том, чтобы оптимизация была включена для всех платформ и целей.
Показать ещё 12 комментариев
Теги:
android-ndk
arm
llvm

1 ответ

4

Мы изучаем этот вопрос. Короче говоря, это зависит от потенциального неправильного выравнивания (до 32 границ) Integer, на которое ссылается указатель. Нужно немного больше времени, чтобы получить ответы на все вопросы... и план для решения этой проблемы.

Марко Канту, модератор Разработчики Delphi

Также ссылается на Почему библиотеки Delphi zlib и zip настолько медленны до 64 бит?, поскольку библиотеки Win64 отправляются без оптимизации.


В отчете QP: RSP-9922 Плохой код ARM, созданный компилятором, директива $O игнорируется?, Марко добавил следующее объяснение:

Здесь есть несколько проблем:

  • Как указано, настройки оптимизации применяются только ко всем файлам устройства, а не к отдельным функциям. Проще говоря, включение и выключение оптимизации в одном файле не будет иметь эффекта.
  • Кроме того, просто включение "Отладочной информации" отключает оптимизацию. Таким образом, при отладке, явное включение оптимизаций не будет иметь никакого эффекта. Следовательно, представление ЦП в среде IDE не сможет отобразить разобранное представление оптимизированного кода.
  • В-третьих, загрузка не выровненных 64-битных данных небезопасна и приводит к ошибкам, а следовательно, к отдельным 4-байтным операциям, которые необходимы в данных сценариях.
  • 0
    Марко Канту опубликовал эту заметку «Мы расследуем проблему» в январе 2015 года, а соответствующий отчет об ошибке RSP-9922 был помечен как исправленный с разрешением «Работает как ожидается» в январе 2016 года, и есть упоминание «внутренняя проблема закрыта 2 марта», 2015" . Я не понимаю их объяснения.
  • 1
    Я добавил комментарий в разрешении проблемы.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню