2011-02-06 9 views
67

Sto lavorando ad un'applicazione Android ad alte prestazioni (un gioco), e sebbene provi prima a scrivere codice per la leggibilità, mi piace tenere in mente un'immagine di ciò che sta accadendo sotto il cofano. Con C++, ho sviluppato un'intuizione abbastanza buona su ciò che il compilatore farà e non farà per me. Sto cercando di fare lo stesso per Java/Android.Quali ottimizzazioni posso aspettarmi da Dalvik e dalla toolchain di Android?

Quindi questa domanda. Potrei trovare molto poco su questo argomento sul web. Il compilatore Java, il convertitore Dalvik (dx) e/o JITter (su Android 2.2+) eseguiranno ottimizzazioni come le seguenti?

  • Metodo di inlining. In quali condizioni? I metodi private possono essere sempre inseriti in modo sicuro; sarà fatto? Che ne dici dei metodi public final? Metodi su oggetti di altre classi? Metodi static? Cosa succede se il tipo di esecuzione dell'oggetto può essere facilmente dedotto dal compilatore? Devo dichiarare metodi come final o static ovunque possibile?

  • Eliminazione di sottoespressione comune. Ad esempio, se accedo due volte allo someObject.someField, la ricerca verrà effettuata una sola volta? E se fosse una chiamata a un getter? Cosa succede se uso due espressioni aritmetiche due volte; sarà valutato solo una volta? Cosa succede se utilizzo il risultato di una determinata espressione, il cui valore non so cambiare, come limite superiore di un ciclo for?

  • I limiti controllano le ricerche di array. La toolchain lo eliminerà in determinate condizioni, come il ciclo archetipico for?

  • Valore inlining. Gli accessi ad alcuni public static final int saranno sempre in linea? Anche se sono in un'altra classe? Anche se sono in un altro pacchetto?

  • Previsione ramo. Quanto è grande un problema anche questo? Si sta ramificando un grande successo di prestazioni su un tipico dispositivo Android?

  • Semplice aritmetica. someInt * 2 sarà sostituito da someInt << 1?

Etcetera ...

+5

Questo potrebbe essere utile: http://developer.android.com/guide/practices/design/performance.html – pablochan

+2

Questi potrebbero anche essere utili: http://www.netmite.com/android/mydroid/dalvik/ docs/dexopt.html, http://www.taranfx.com/android-internals-jit-froyo e la versione pdf della presentazione: http://www.android-app-developer.co.uk/android- app-development-docs/android-jit-compiler-androids-dalvik-vm.pdf – Lior

+0

@pablochan: Quella è stata sotto il mio cuscino per qualche tempo, ma grazie :) @Lior: Buoni riferimenti, quelli sono nuovi per me, grazie ! – Thomas

risposta

102

Questo è Ben, uno degli ingegneri che lavorano su JIT @ Google. Quando io e Bill abbiamo iniziato questo progetto, l'obiettivo era quello di fornire un JIT funzionante il prima possibile con un impatto minimo sulla contesa delle risorse (ad es. Ingombro della memoria, CPU dirottata dal thread del compilatore) in modo che possa essere eseguito su dispositivi di fascia bassa come bene. Quindi abbiamo usato un modello basato su tracce molto primitivo. Cioè, l'entità di compilazione passata al compilatore JIT è un blocco di base, a volte più breve di una singola istruzione. Tali tracce verranno unite insieme in fase di esecuzione attraverso una tecnica chiamata concatenazione in modo che la ricerca di interprete e cache del codice non venga invocata spesso. In una certa misura, la principale fonte di accelerazione deriva dall'eliminazione del sovraccarico dell'interprete ripetuto durante l'analisi dei percorsi di codice eseguiti di frequente.

Detto questo, noi abbiamo un bel paio di ottimizzazioni locali attuate con il JIT Froyo:

  • allocazione Register (8 registri per bersaglio v5te dal momento che il JIT produce codice Thumb/16 registri per V7)
  • Pianificazione (ad es. Eliminazione ridondante di Ld/St per i registri di Dalvik, sollevamento del carico, affondamento del magazzino)
  • Eliminazione del controllo nullo ridondante (se tale ridondanza può essere trovata in un blocco di base).
  • Formazione di loop e ottimizzazione per semplici cicli di conteggio (ovvero senza uscita laterale nel corpo del loop). Per tali cicli, gli accessi agli array basati su variabili di induzione estese sono ottimizzati in modo che i controlli nulli e di intervallo vengano eseguiti solo nel prologo del ciclo.
  • Una voce cache in linea per sito virtuale con patch dinamiche in fase di esecuzione.
  • Ottimizzazioni degli spioncini come la riduzione della potenza sugli operandi letterali per mul/div.

In Gingerbread abbiamo aggiunto la semplice inlining per getter/setter. Dal momento che il frontend JIT sottostante è ancora basato su una traccia semplice, se il callee ha rami in esso non sarà inline. Ma il meccanismo di cache inline è implementato in modo tale che i getter/setter virtuali possano essere allineati senza problemi.

Attualmente stiamo lavorando all'ampliamento dell'ambito di compilazione oltre una semplice traccia in modo che il compilatore abbia una finestra più ampia per l'analisi e l'ottimizzazione del codice. Rimanete sintonizzati.

+1

Grazie per la condivisione! –

+11

Ti sei registrato specificamente per rispondere a questo? Grazie! Peccato che gli operatori siano così lenti nel tirare fuori il Gingerbread; non posso contare su quelle ottimizzazioni per almeno un altro anno credo. Questa è tutta roba buona, ma dal momento che non sono uno scrittore di compilatori, ho difficoltà a vedere come applicarlo nella pratica. In particolare: ... – Thomas

+2

(1) L'ottimizzazione del ciclo viene eseguita anche se il limite superiore del ciclo dipende da una variabile non finale (ad esempio un campo), o dovrei memorizzare il limite superiore in una variabile locale finale? (2) Stessa domanda per tutti i campi a cui si accede ripetutamente. (3) Devo dichiarare i miei metodi 'final' ove possibile? Oppure conta ancora come un sito virtuale? (4) Inlining in generale non è stato fatto a livello di JIT, ma forse capita di sapere se lo strumento compilatore e/o dx lo fa? – Thomas

10

Sono sicuro che la mia risposta non risponderà a tutte le vostre domande, ma credo che sia una vittoria se risponde anche una sola.

Sembra che tu abbia una profonda conoscenza dell'argomento e sappi cosa vuoi, quindi potresti voler fare quanto segue. Costruisci un'applicazione di esempio contenente gli aspetti su cui vuoi investigare.

Prendere l'APK che si ottiene e eseguirlo attraverso il APK Tool. Il reverse engineering del tuo codice per fare solo quello che intendi è perfettamente perfetto come sappiamo.

L'APK Tool estrae e decodifica le risorse e invierà i file .dex ai file .smali. È possibile che si desideri cercare anche il progetto smali per ottenere ulteriori informazioni su come leggere i file .smali e sui relativi limiti.

Ancora una volta sono abbastanza sicuro che questo non ha intenzione di rispondere a tutte le vostre domande, ma potrebbe essere un buon inizio.

+1

Buona risposta, grazie. Quella linea di investigazione non mi era ancora venuta in mente, principalmente perché ci sarebbe voluto molto tempo. Ciò mostrerebbe quantomeno ciò che il compilatore e il dx Java stanno facendo, sebbene gli effetti del JITter rimangano incerti. Se mi sento curioso e percorro questa strada, posterò sicuramente i miei risultati qui. – Thomas

+0

Sì, per favore fallo. Sono abbastanza interessato al risultato me stesso. –

+1

javac esegue alcune ottimizzazioni ma nulla di drammatico. "dx" fornisce una conversione fedele del suo input. Come Ben ha sottolineato, se queste cose non fossero vere, faresti molto fatica con i debugger. Per un esempio pratico, vedi http://groups.google.com/group/android-platform/browse_thread/thread/e4749164474fb429/93901e2e43a657c8 (in particolare la parte in cui "dx" produce codice migliore se non si passa " -g "a javac). Dovresti anche esaminare le ottimizzazioni di ProGuard. – fadden

5

Per prima cosa, lascia che ti precisi dicendo che non sono un esperto di dalvik e che alcune delle mie risposte potrebbero essere sbagliate. Ma ho scavato nel codice JIT in dalvik, e ho abbastanza familiarità con il bytecode che dalvik funziona.

  1. Inlining del metodo - per quanto ne so, questo non succede mai. Sono quasi certo che non succede mai a livello di bytecode, e non credo che al momento avvenga a livello di JIT, anche se potrebbe accadere in futuro.

  2. Eliminazione di sottoespressione comune: credo che ciò sarebbe possibile solo per le sottoespressioni che non utilizzano variabili/campi non finali. Non sono del tutto positivo se accadrebbe anche allora. Se è fatto, mi aspetto che sia fatto a livello di bytecode, probabilmente non a livello di JIT.

  3. controllo dei limiti sulle ricerche Array - Nessun indizio

  4. Valore inline - Per quanto ne so, sì - saranno inline in tutti questi scenari.

  5. Branch previsione - non sono sicuro

  6. semplice aritmetica - non per quanto ne so

Inoltre, vorrei citare un altro viale di approccio a voi - dx e Dalvik sono sia open source, quindi puoi scavare in loro tutto ciò che ti piace. Sebbene, ovviamente, non siano codebase di piccole dimensioni, quindi ci vorrebbe un bel po 'di sforzi per scavare in essi a quel livello

+0

Beh, se questo è qualcosa da fare, ho fatto bene a disegnare a mano i miei metodi e memorizzare i risultati di subexpression nella cache. Grazie! – Thomas