Come influire sulla generazione di codice Delphi XEx per target Android / ARM?


266

Aggiornamento 2017-05-17. Non lavoro più per l'azienda da cui è nata questa domanda e non ho accesso a Delphi XEx. Mentre ero lì, il problema è stato risolto migrando verso FPC + GCC misti (Pascal + C), con intrinseci NEON per alcune routine in cui ha fatto la differenza. (FPC + GCC è altamente raccomandato anche perché consente di utilizzare strumenti standard, in particolare Valgrind.) Se qualcuno può dimostrare, con esempi credibili, come sono effettivamente in grado di produrre codice ARM ottimizzato da Delphi XEx, sono felice di accettare la risposta .


I compilatori Delphi di Embarcadero utilizzano un back-end LLVM per produrre codice ARM nativo per dispositivi Android. Ho una grande quantità di codice Pascal che devo compilare in applicazioni Android e vorrei sapere come fare in modo che Delphi generi codice più efficiente. In questo momento, non sto nemmeno parlando di funzionalità avanzate come le ottimizzazioni SIMD automatiche, ma solo di produrre codice ragionevole. Sicuramente ci deve essere un modo per passare i parametri al lato LLVM o in qualche modo influenzare il risultato? Di solito, qualsiasi compilatore avrà molte opzioni per influenzare la compilazione e l'ottimizzazione del codice, ma gli obiettivi ARM di Delphi sembrano essere solo "ottimizzazione on / off" e basta.

Si suppone che LLVM sia in grado di produrre codice ragionevolmente stretto e ragionevole, ma sembra che Delphi stia usando le sue strutture in un modo strano. Delphi vuole usare lo stack molto pesantemente e generalmente utilizza solo i registri del processore r0-r3 come variabili temporanee. Forse il più folle di tutti, sembra caricare normali numeri interi a 32 bit come quattro operazioni di caricamento da 1 byte. Come fare in modo che Delphi produca un codice ARM migliore e senza la seccatura byte per byte che sta causando per Android?

Inizialmente pensavo che il caricamento byte per byte servisse a scambiare l'ordine dei byte da big-endian, ma non era così, in realtà stava solo caricando un numero a 32 bit con 4 carichi a byte singolo. * Potrebbe essere il caricamento i 32 bit completi senza eseguire un caricamento della memoria di dimensioni non allineate di parole. (se DOVREBBE evitare che sia un'altra cosa, che suggerirebbe che si tratta di un bug del compilatore) *

Diamo un'occhiata a questa semplice funzione:

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

Anche con le ottimizzazioni attivate, Delphi XE7 con il pacchetto di aggiornamento 1 e XE6 producono il seguente codice assembly ARM per quella funzione:

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}

Basta contare il numero di istruzioni e gli accessi alla memoria necessari a Delphi. E costruendo un numero intero a 32 bit da 4 carichi a byte singolo ... Se cambio un po 'la funzione e utilizzo un parametro var anziché un puntatore, è leggermente meno contorto:

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}

Non includerò lo smontaggio qui, ma per iOS, Delphi produce un codice identico per le versioni dei puntatori e dei parametri var e sono quasi ma non esattamente gli stessi della versione dei parametri var di Android. Modifica: per chiarire, il caricamento byte per byte è solo su Android. E solo su Android, le versioni del puntatore e del parametro var differiscono l'una dall'altra. Su iOS entrambe le versioni generano esattamente lo stesso codice.

Per fare un confronto, ecco cosa pensa FPC 2.7.1 (versione trunk SVN da marzo 2014) della funzione con livello di ottimizzazione -O2. Le versioni del puntatore e del parametro var sono esattamente le stesse.

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

Ho anche testato una funzione C equivalente con il compilatore C fornito con Android NDK.

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

E questo si compone essenzialmente della stessa cosa fatta da FPC:

Disassembly of section .text._Z11ReadIntegerPi:

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

14
A proposito nella discussione di Google+ su questo, Sam Shaw osserva che C ++ mostra il codice di lunga durata nelle build di debug e il codice ottimizzato in rilascio. Dove Delphi lo fa in entrambi. Quindi potrebbe essere un semplice bug tra le bandiere che stanno inviando LLVM, e in tal caso vale la pena archiviare una segnalazione di bug, che potrebbe essere risolta molto presto.
David,

9
Oh, ok, ho letto male. Quindi, come ha detto Notlikethat, sembra che supponga che il carico del puntatore non sarà allineato (o non possa garantire l'allineamento) e che le piattaforme ARM più vecchie non possano necessariamente fare carichi non allineati. Assicurati di avere il targeting build armeabi-v7ainvece di armeabi(non sono sicuro se ci sono tali opzioni in questo compilatore), poiché i carichi non allineati dovrebbero essere supportati da ARMv6 (mentre armeabipresuppone ARMv5). (Lo smontaggio mostrato non sembra leggere un valore bigendiano, legge solo un piccolo valore endian un byte alla volta.)
mstorsjo

6
Ho trovato RSP-9922 che sembra essere lo stesso bug.
David,

6
Qualcuno aveva chiesto che l'ottimizzazione venisse interrotta tra XE4 e XE5, nel newsgroup embarcadero.public.delphi.platformspecific.ios, "Ottimizzazione del compilatore ARM interrotta?" devsuperpage.com/search/…
Side S. Fresh

6
@Johan: che eseguibile è? Ho avuto l'impressione che fosse in qualche modo cotto nell'eseguibile del compilatore di Delphi. Provalo e facci sapere i risultati.
Side S. Fresh

Risposte:


8

Stiamo esaminando il problema. In breve, dipende dal potenziale disallineamento (al limite di 32) dell'intero a cui fa riferimento un puntatore. Hai bisogno di un po 'più di tempo per avere tutte le risposte ... e un piano per affrontarlo.

Marco Cantù, moderatore di Delphi Developers

Anche riferimento Perché le librerie Delphi zlib e zip sono così lente sotto i 64 bit? poiché le librerie Win64 vengono fornite costruite senza ottimizzazioni.


Nel rapporto QP: codice ARM RSP-9922 errato prodotto dal compilatore, la direttiva $ O ignorata? , Marco ha aggiunto la seguente spiegazione:

Ci sono più problemi qui:

  • Come indicato, le impostazioni di ottimizzazione si applicano solo a interi file di unità e non a singole funzioni. In poche parole, l'attivazione e la disattivazione dell'ottimizzazione nello stesso file non avrà alcun effetto.
  • Inoltre, la semplice attivazione di "Informazioni di debug" disattiva l'ottimizzazione. Pertanto, quando si esegue il debug, l'attivazione esplicita delle ottimizzazioni non avrà alcun effetto. Di conseguenza, la vista CPU nell'IDE non sarà in grado di visualizzare una vista disassemblata di codice ottimizzato.
  • In terzo luogo, il caricamento di dati a 64 bit non allineati non è sicuro e comporta errori, quindi le operazioni separate da 4 byte a un byte necessarie in determinati scenari.

Marco Cantù ha pubblicato la nota "Stiamo esaminando il problema" a gennaio 2015 e la relativa segnalazione di bug RSP-9922 è stata contrassegnata come risolta con la risoluzione "Funziona come previsto" a gennaio 2016, e c'è una menzione "problema interno chiuso il 2 marzo, 2015" . Non capisco le loro spiegazioni.
Side S. Fresh,

1
Ho aggiunto un commento nella risoluzione del problema.
Marco Cantù,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.