web-dev-qa-db-fra.com

Comment affecter la génération de code Delphi XEx pour les cibles Android / ARM?

Mise à jour 2017-05-17. Je ne travaille plus pour l'entreprise à l'origine de cette question et je n'ai pas accès à Delphi XEx. Pendant mon séjour, le problème a été résolu en passant à un système mixte FPC + GCC (Pascal + C), avec des éléments intrinsèques NEON pour certaines routines où cela a fait une différence (FPC + GCC est fortement recommandé, car il permet également d’utiliser des outils standard, en particulier Valgrind.) Si quelqu'un peut démontrer, avec des exemples crédibles, comment il est réellement capable produire optimisé ARM de Delphi XEx, je suis ravi d’accepter la réponse.


Les compilateurs Delphi d’Embarcadero utilisent un système LLVM pour produire des périphériques ARM pour des périphériques Android). J'ai d’importantes quantités de code Pascal à compiler = Android applications et j’aimerais savoir comment faire en sorte que Delphi génère un code plus efficace. Pour le moment, je ne parle même pas de fonctionnalités avancées telles que l’optimisation automatique SIMD, mais simplement la production de code raisonnable. Un moyen de passer des paramètres du côté de LLVM, ou d’affecter le résultat? En général, tout compilateur aura de nombreuses options pour affecter la compilation et l’optimisation du code, mais les cibles de Delphi ARM semblent être simplement "optimisation on/off "et c'est tout.

LLVM est censé être capable de produire un code raisonnablement serré et raisonnable, mais il semble que Delphi utilise ses installations de manière étrange. Delphi souhaite utiliser la pile très fréquemment et n'utilise généralement que les registres du processeur r0-r3 en tant que variables temporaires. Peut-être le plus fou de tous, il semble charger des entiers normaux 32 bits sous la forme de quatre opérations de chargement sur un octet. Comment faire en sorte que Delphi produise un meilleur code ARM), et sans les tracas octet par octet qu’il engendre pour Android?

Au début, je pensais que le chargement octet par octet était destiné à permuter l'ordre des octets de big-endian, mais ce n'était pas le cas. Il s'agit simplement de charger un nombre 32 bits avec 4 charges d'un octet. * Cela pourrait être de charger les 32 bits complets sans effectuer de charge mémoire non alignée de la taille d'un mot. (savoir si cela DEVRAIT éviter cela est une autre chose, ce qui laisserait penser que tout cela est un bug du compilateur) *

Regardons cette fonction simple:

function ReadInteger(APInteger : PInteger) : Integer;
begin
  Result := APInteger^;
end;

Même avec les optimisations activées, Delphi XE7 avec le pack de mise à jour 1, ainsi que XE6, génèrent le code ARM) suivant: code d'assemblage de cette fonction:

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}

Comptez simplement le nombre d'instructions et d'accès mémoire nécessaires à Delphi. Et construire un entier 32 bits à partir de 4 charges sur un seul octet ... Si je modifie un peu la fonction et utilise un paramètre var au lieu d'un pointeur, il est un peu moins compliqué:

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}

Je ne vais pas inclure le désassemblage ici, mais pour iOS, Delphi produit un code identique pour les versions de pointeur et de paramètre var, qui sont presque identiques à la version du paramètre Android var. Édition: pour clarifier, le chargement octet par octet ne concerne que Android. Et uniquement sous Android, les versions du pointeur et du paramètre var diffèrent. Sur iOS, les deux versions génèrent exactement le même code.

À titre de comparaison, voici ce que FPC 2.7.1 (version de la ligne de réseau SVN de mars 2014) pense de la fonction avec le niveau d'optimisation -O2. Les versions de pointeur et de paramètre var sont exactement les mêmes.

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

J'ai également testé une fonction C équivalente avec le compilateur C fourni avec le Android NDK.

int ReadInteger(int *APInteger)
{
    return *APInteger;
}

Et cela résume essentiellement la même chose que FPC a faite:

Disassembly of section .text._Z11ReadIntegerPi:

00000000 <_Z11ReadIntegerPi>:
   0:   6800        ldr r0, [r0, #0]
   2:   4770        bx  lr
264
Side S. Fresh

Nous étudions le problème. En bref, cela dépend du mauvais alignement potentiel (à la limite de 32) de l'entier référencé par un pointeur. Besoin d'un peu plus de temps pour avoir toutes les réponses ... et un plan pour y remédier.

Marco Cantù, modérateur de développeurs Delphi

Référence également Pourquoi les bibliothèques Delphi zlib et Zip sont-elles si lentes en 64 bits? car les bibliothèques Win64 sont livrées construites sans optimisations.


Dans le rapport QP: RSP-9922 Bad ARM généré par le compilateur, directive $ O ignorée? ), Marco ajouta l'explication suivante:

Il y a plusieurs problèmes ici:

  • Comme indiqué, les paramètres d'optimisation s'appliquent uniquement à des fichiers d'unité complets et non à des fonctions individuelles. Autrement dit, l'activation et la désactivation de l'optimisation dans le même fichier n'auront aucun effet.
  • De plus, le simple fait d'activer les "informations de débogage" désactive l'optimisation. Ainsi, quand on est en train de déboguer, activer explicitement les optimisations n'aura aucun effet. Par conséquent, la vue CPU de la IDE) ne pourra pas afficher une vue désassemblée du code optimisé.
  • Troisièmement, le chargement de données 64 bits non alignées n’est pas sûr et entraîne des erreurs, d’où les opérations distinctes à 4 octets nécessaires dans des scénarios donnés.
6
Kirk Strobeck