2012-02-14 10 views
18

Utilizamos una carga de Google Guava LoadingCache para mapas de bits en una aplicación de Android. En la aplicación estoy ejecutando un hilo de dibujo, que pinta los mapas de bits en el caché de un lienzo. Si un mapa de bits específico no está en la memoria caché, no se dibuja para que ninguna carga pueda bloquear el hilo de dibujo.Mal rendimiento con Guava Cache en Android

Sin embargo, la pintura produce un tartamudeo visual y la velocidad de cuadros por segundo no es la que nos gustaría. Me clavé en el método getIfPresent() de la memoria caché. Eso solo toma más del 20% del tiempo total de CPU de las aplicaciones. En getIfPresent()LocalCache$Segment.get() se hace cargo del 80% del tiempo:

profiling-guava-cache.jpg

Tenga en cuenta, esto es sólo una búsqueda de un mapa de bits ya presente. Nunca ocurrirá una carga en get(). Pensé que habría una sobrecarga de contabilidad en get() para la cola LRU que decide qué desalojo se lleva a cabo si el segmento está lleno. Pero esto es al menos un orden de magnitud más lento de lo que me daría un Key-Lookup en LRU-LinkedHashmap.get().

Usamos un caché para obtener búsquedas rápidas si un elemento está en el caché, si la búsqueda es lenta, no tiene sentido almacenarlo en caché. También probé getAllPresent(a) y asMap() pero da el mismo rendimiento.

versión de la Biblioteca es: guayaba-11.0.1.jar

LoadingCache se define como sigue:

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); 
      } 
     }); 

Mis preguntas son:

  • lo uso ¿incorrecto?
  • ¿Su implementación no es compatible con Android?
  • ¿Echaba de menos una opción de configuración?
  • ¿Es este un problema conocido con la caché en la que se está trabajando?

Actualización:

Después de cerca de 100 cuadros pintados los CacheStats son:

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

Después de eso misscount se mantiene básicamente los mismos que los incrementos HitCount. En este caso, el caché es lo suficientemente grande para que las cargas sucedan escasamente, pero getIfPresent es lento, no obstante.

+1

Por favor, no negrita cada dos frases; era difícil de leer, así que envié una edición para eliminarla. – simchona

+0

Gracias, simchona por editarlo para hacerlo mejor legible. – user643011

+0

¿Podría publicar los resultados de 'tiles.cacheStats()'? –

Respuesta

30

CacheBuilder fue diseñado para el almacenamiento en memoria caché del lado del servidor, donde la concurrencia era una preocupación principal. Por lo tanto, intercambia una sobrecarga de una sola hebra y de memoria a cambio de un mejor comportamiento de subprocesos múltiples. Los desarrolladores de Android deben usar LruCache, LinkedHashMap, o similar, donde el rendimiento y la memoria de un único subproceso son las principales preocupaciones. En el futuro, puede haber un concurrencyLevel = 0 para indicar que se requiere un caché liviano y no simultáneo.

+1

He intentado concurrencyLevel = 1, pero no mejoró el tiempo de acceso. concurrencyLevel = 0 lleva a un error. De hecho, AtomicReferenceArray toma una cantidad significativa de tiempo de CPU. Iré por android.util.LruCache por ahora. Gracias por su comentario sobre la arquitectura ARM y su respuesta detallada. Me encantaría ver un concurrencyLevel = 0 en el futuro. – user643011

+1

concurrencyLevel = 1 significa escritor único, lectores múltiples. Eso no proporciona mucha simplificación para trabajar. Un nivel de 0 indicaría que los lectores están sincronizados con las escrituras, por lo que podría implementarse una versión sincronizada. Cuando Código examinó LRUCache que aún estaban trabajando en la interfaz de caché, así que era de baja prioridad para agregar concurrencyLevel = 0 a Map Maker. Así nació LruCache. –

+4

FYI, LRUCache también está disponible pre-Honeycomb en la biblioteca de compatibilidad. http://developer.android.com/sdk/compatibility-library.html –

2

El código de Android no siempre se optimiza de la misma forma que en una JVM. Lo que puede funcionar muy bien en Java podría no funcionar tan bien en Android. Te sugiero que escribas un caché muy simple. p.ej. usando LinkedHashMap.removeEldestEntry() y vea cómo va eso.

Cuestiones relacionadas