Wie kann die Delphi XEx-Codegenerierung für Android/ARM-Ziele beeinflusst werden?

Lesezeit: 6 Minuten

Benutzer-Avatar
Seite S. Frisch

Update 17.05.2017. Ich arbeite nicht mehr für das Unternehmen, aus dem diese Frage stammt, und habe keinen Zugriff auf Delphi XEx. Während ich dort war, wurde das Problem durch die Migration auf gemischte FPC+GCC (Pascal+C) gelöst, mit NEON-Intrinsics für einige Routinen, wo es einen Unterschied machte. (FPC+GCC wird auch deshalb dringend empfohlen, weil es die Verwendung von Standardwerkzeugen, insbesondere Valgrind, ermöglicht.) Wenn jemand mit glaubwürdigen Beispielen zeigen kann, wie er tatsächlich in der Lage ist, optimierten ARM-Code aus Delphi XEx zu erzeugen, nehme ich die Antwort gerne an .


Die Delphi-Compiler von Embarcadero verwenden ein LLVM-Backend, um nativen ARM-Code für Android-Geräte zu erzeugen. Ich habe große Mengen an Pascal-Code, den ich in Android-Anwendungen kompilieren muss, und ich würde gerne wissen, wie ich Delphi dazu bringen kann, effizienteren Code zu generieren. Im Moment spreche ich nicht einmal von erweiterten Funktionen wie automatischen SIMD-Optimierungen, sondern nur davon, vernünftigen Code zu produzieren. Sicherlich muss es eine Möglichkeit geben, Parameter an die LLVM-Seite zu übergeben oder das Ergebnis irgendwie zu beeinflussen? Normalerweise hat jeder Compiler viele Optionen, um die Codekompilierung und -optimierung zu beeinflussen, aber die ARM-Ziele von Delphi scheinen nur “Optimierung ein/aus” zu sein, und das war’s.

LLVM soll in der Lage sein, relativ straffen und vernünftigen Code zu produzieren, aber es scheint, dass Delphi seine Möglichkeiten auf seltsame Weise nutzt. Delphi möchte den Stack sehr intensiv nutzen und verwendet im Allgemeinen nur die Register r0-r3 des Prozessors als temporäre Variablen. Das vielleicht verrückteste von allen scheint es, normale 32-Bit-Ganzzahlen als vier 1-Byte-Ladeoperationen zu laden. Wie lässt sich Delphi dazu bringen, besseren ARM-Code zu produzieren, und zwar ohne den Byte-für-Byte-Aufwand, den es für Android verursacht?

Zuerst dachte ich, dass das Byte-für-Byte-Laden dazu dient, die Byte-Reihenfolge von Big-Endian auszutauschen, aber das war nicht der Fall, es wird wirklich nur eine 32-Bit-Zahl mit 4 Einzelbyte-Ladevorgängen geladen. * Es könnte sein Laden die vollen 32 Bits, ohne eine nicht ausgerichtete Speicherladung in Wortgröße durchzuführen. (ob es das vermeiden SOLLTE, ist eine andere Sache, was darauf hindeuten würde, dass das Ganze ein Compiler-Fehler ist) *

Schauen wir uns diese einfache Funktion an:

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

Auch mit eingeschalteten Optimierungen erzeugen Delphi XE7 mit Update Pack 1 sowie XE6 den folgenden ARM-Assembler-Code für diese Funktion:

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}

Zählen Sie einfach die Anzahl der Befehle und Speicherzugriffe, die Delphi dafür benötigt. Und eine 32-Bit-Ganzzahl aus 4 Einzelbyte-Ladevorgängen zu konstruieren … Wenn ich die Funktion ein wenig ändere und einen var-Parameter anstelle eines Zeigers verwende, ist es etwas weniger kompliziert:

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}

Ich werde die Disassemblierung hier nicht einschließen, aber für iOS erzeugt Delphi identischen Code für die Pointer- und var-Parameterversionen, und sie sind fast, aber nicht genau gleich wie die Android-var-Parameterversion.
Bearbeiten: Zur Verdeutlichung erfolgt das byteweise Laden nur auf Android. Und nur auf Android unterscheiden sich die Zeiger- und Var-Parameterversionen voneinander. Unter iOS generieren beide Versionen genau den gleichen Code.

Zum Vergleich hier, was FPC 2.7.1 (SVN-Trunk-Version vom März 2014) von der Funktion mit Optimierungsstufe -O2 hält. Die Zeiger- und Var-Parameterversionen sind genau gleich.

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

Ich habe auch eine äquivalente C-Funktion mit dem C-Compiler getestet, der mit dem Android NDK geliefert wird.

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

Und dies kompiliert im Wesentlichen zu dem, was FPC gemacht hat:

Disassembly of section .text._Z11ReadIntegerPi:

00000000 <_Z11ReadIntegerPi>:
   0:   6800        ldr r0, [r0, #0]
   2:   4770        bx  lr

  • Bzw im Google+ Diskussion dazu merkt Sam Shaw an, dass C++ den Langformcode in Debug-Builds und den optimierten Code in der Veröffentlichung anzeigt. Wobei Delphi es in beiden tut. Daher könnte es sich durchaus um einen einfachen Fehler in den Flags handeln, die sie an LLVM senden, und wenn es sich lohnt, einen Fehlerbericht einzureichen, wird er möglicherweise bald behoben.

    – David

    14. Januar 2015 um 17:06 Uhr

  • Ah, ok, ich habe mich verlesen. Dann, wie Notlikethat sagte, klingt es so, als würde es davon ausgehen, dass das Laden des Zeigers nicht ausgerichtet wäre (oder die Ausrichtung nicht garantieren kann), und ältere ARM-Plattformen können nicht unbedingt nicht ausgerichtete Lasten ausführen. Stellen Sie sicher, dass Sie das Targeting erstellen armeabi-v7a Anstatt von armeabi (nicht sicher, ob es solche Optionen in diesem Compiler gibt), da nicht ausgerichtete Ladevorgänge seit ARMv6 unterstützt werden sollten (während armeabi setzt ARMv5 voraus). (Die gezeigte Disassemblierung sieht nicht so aus, als würde sie einen Bigendian-Wert lesen, sie liest nur Byte für Byte einen Little-Endian-Wert.)

    – mstorsjo

    14. Januar 2015 um 17:07 Uhr

  • ich fand RSP-9922 das scheint derselbe Fehler zu sein.

    – David

    16. Januar 2015 um 11:48 Uhr

  • Jemand hatte in der Newsgroup embarcadero.public.delphi.platformspecific.ios gefragt, ob die Optimierung zwischen XE4 und XE5 unterbrochen wird, “ARM-Compiler-Optimierung defekt?” devsuperpage.com/search/…

    – Seite S. Frisch

    26. Januar 2015 um 9:30 Uhr


  • @Johan: Welche ausführbare Datei ist das? Ich hatte den Eindruck, dass es irgendwie in Delphis ausführbare Compiler-Datei gebacken wurde. Probieren Sie es aus und teilen Sie uns die Ergebnisse mit.

    – Seite S. Frisch

    3. August 2015 um 12:56 Uhr

Benutzer-Avatar
Kirk Strobeck

Wir untersuchen das Problem. Kurz gesagt, es hängt von der möglichen Fehlausrichtung (an der 32-Grenze) der Ganzzahl ab, auf die von einem Zeiger verwiesen wird. Brauchen Sie ein wenig mehr Zeit, um alle Antworten zu haben … und einen Plan, um dies anzugehen.

Marco Cantù, Moderator auf Delphi-Entwickler

Siehe auch Warum sind die zlib- und zip-Bibliotheken von Delphi unter 64-Bit so langsam? da Win64-Bibliotheken ohne Optimierungen gebaut ausgeliefert werden.


Im QP-Bericht: RSP-9922 Fehlerhafter ARM-Code vom Compiler erzeugt, $O-Direktive ignoriert?Marco fügte folgende Erklärung hinzu:

Hier gibt es mehrere Probleme:

  • Wie bereits erwähnt, gelten Optimierungseinstellungen nur für ganze Unit-Dateien und nicht für einzelne Funktionen. Einfach ausgedrückt, das Ein- und Ausschalten der Optimierung in derselben Datei hat keine Auswirkung.
  • Darüber hinaus wird die Optimierung einfach deaktiviert, wenn “Debug-Informationen” aktiviert sind. Daher hat das explizite Einschalten von Optimierungen beim Debuggen keine Auswirkung. Folglich kann die CPU-Ansicht in der IDE keine zerlegte Ansicht des optimierten Codes anzeigen.
  • Drittens ist das Laden von nicht ausgerichteten 64-Bit-Daten nicht sicher und führt zu Fehlern, daher die separaten 4 Ein-Byte-Operationen, die in bestimmten Szenarien erforderlich sind.

  • Marco Cantù hat diese Notiz „Wir untersuchen das Problem“ im Januar 2015 gepostet, und der zugehörige Fehlerbericht RSP-9922 wurde im Januar 2016 mit der Lösung „Funktioniert wie erwartet“ als gelöst markiert, und es gibt eine Erwähnung „internes Problem geschlossen am 2. 2015″. Ich verstehe ihre Erklärungen nicht.

    – Seite S. Frisch

    26. Juli 2016 um 6:48 Uhr

  • Ich habe einen Kommentar in der Problemlösung hinzugefügt.

    – Marco Cantu

    7. Dezember 2016 um 13:42 Uhr


1350580cookie-checkWie kann die Delphi XEx-Codegenerierung für Android/ARM-Ziele beeinflusst werden?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy