2012-02-14 14 views
18

Utilizziamo un caricamento Google Guava LoadingCache per bitmap in un'applicazione Android. Nell'applicazione sto eseguendo un Thread di disegno, che dipinge le bitmap nella cache su un Canvas. Se una bitmap specifica non è nella cache, non viene disegnata in modo che nessun caricamento bloccherà mai il Thread del disegno.Scarse prestazioni con Guava Cache su Android

Tuttavia, il dipinto genera una balbuzie visiva e la velocità dei fotogrammi al secondo non è quella che vorremmo. L'ho inchiodato al metodo getIfPresent() della cache. Questo da solo occupa oltre il 20% del tempo totale della CPU delle applicazioni. In getIfPresent()LocalCache$Segment.get() riprende 80% del tempo:

profiling-guava-cache.jpg

tenga presente, questa è solo una ricerca di un già presenti bitmap. Non ci sarà mai un carico in get(). Ho pensato che ci sarebbe stato un overhead contabile in get() per la coda LRU che decide quale sfratto si verifica se il segmento è pieno. Ma questo è almeno un ordine di grandezza più lento di quello che mi darebbe un in LRU-LinkedHashmap.get().

Utilizziamo una cache per ottenere ricerche veloci se un elemento è nella cache, se la ricerca è lenta, non è necessario memorizzarla nella cache. Ho anche provato getAllPresent(a) e asMap() ma offre prestazioni uguali.

versione Library è: guava-11.0.1.jar

LoadingCache è definito come segue:

LoadingCache<TileKey, Bitmap> tiles = CacheBuilder.newBuilder().maximumSize(100).build(new CacheLoader<TileKey,Bitmap>() { 
      @Override 
      public Bitmap load(TileKey tileKey) { 
      System.out.println("Loading in " + Thread.currentThread().getName() + " " 
       + tileKey.x + "-" + tileKey.y); 

      final File[][] tileFiles = surfaceState.mapFile.getBuilding() 
       .getFloors().get(tileKey.floorid) 
       .getBackground(tileKey.zoomid).getTileFiles(); 
      String tilePath = tileFiles[tileKey.y][tileKey.x].getAbsolutePath(); 

      Options options = new BitmapFactory.Options(); 
      options.inPreferredConfig = Bitmap.Config.RGB_565; 

      return BitmapFactory.decodeFile(tilePath, options); 
      } 
     }); 

Le mie domande sono:

  • faccio a utilizzarlo sbagliato?
  • È un'implementazione non utilizzabile per Android?
  • Mi manca un'opzione di configurazione?
  • Si tratta di un problema noto con la cache su cui si sta lavorando?

Aggiornamento:

Dopo circa 100 cornici dipinte le CacheStats sono:

I/System.out(6989): CacheStats{hitCount=11992, missCount=97, 
loadSuccessCount=77, loadExceptionCount=0, totalLoadTime=1402984624, evictionCount=0} 

Dopo che missCount rimane fondamentalmente lo stesso di incrementi HitCount. In questo caso la cache è abbastanza grande perché i carichi si verifichino scarsamente, ma getIfPresent è lento.

+1

Si prega di fare non in grassetto ogni altra frase; era difficile da leggere, quindi ho inviato una modifica per eliminarla. – simchona

+0

Grazie, simchona per averlo modificato per renderlo più leggibile. – user643011

+0

Potresti pubblicare i risultati di 'tiles.cacheStats()'? –

risposta

30

CacheBuilder è stato progettato per la memorizzazione nella cache sul lato server, dove la concorrenza era una preoccupazione principale. Si scambia quindi l'overhead a thread singolo e di memoria in cambio di un migliore comportamento multi-thread. Gli sviluppatori Android dovrebbero utilizzare LruCache, LinkedHashMap o simili in cui le prestazioni e la memoria a thread singolo sono i problemi principali. In futuro potrebbe esserci un concurrencyLevel = 0 per indicare che è necessaria una cache leggera e non simultanea.

+1

Ho provato concurrencyLevel = 1, ma non ha migliorato il tempo di accesso. concurrencyLevel = 0 porta a un errore. Infatti, AtomicReferenceArray richiede una quantità significativa di tempo CPU. Andrò per android.util.LruCache per ora. Grazie per il tuo commento riguardante l'architettura ARM e la tua risposta dettagliata. Mi piacerebbe vedere un concurrencyLevel = 0 in futuro. – user643011

+1

concurrencyLevel = 1 significa single writer, più lettori. Ciò non fornisce una gran semplificazione su cui lavorare. Un livello pari a 0 indica che i lettori sono sincronizzati con le scritture, pertanto è possibile implementare una versione sincronizzata. Quando ho letto il codice LruCache, stavamo ancora lavorando all'interfaccia Cache, quindi era bassa priorità aggiungere concurrencyLevel = 0 a MapMaker. Così è nato LruCache. –

+4

FYI, LruCache è disponibile anche pre-Honeycomb nella libreria di compatibilità. http://developer.android.com/sdk/compatibility-library.html –

2

Il codice Android non è sempre ottimizzato nello stesso modo in cui si trova in una JVM. Ciò che può funzionare molto bene in Java potrebbe non funzionare altrettanto bene in Android. Ti suggerisco di scrivere una cache molto semplice. per esempio. usando LinkedHashMap.removeEldestEntry() e vedi come va.

Problemi correlati