Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Bad performance with Guava Cache on Android

Tags:

We use a loading Google Guava LoadingCache for bitmaps in an Android application. In the application I am running a drawing Thread, that paints the bitmaps in the cache to a Canvas. If a specific bitmap is not in the cache, it does not get drawn so that no loading will ever block the drawing Thread.

However, the painting results in visual stuttering and the frames per second rate is not how we would like it. I nailed it down to the getIfPresent() method of the cache. That alone takes over 20% of the applications total CPU time. In getIfPresent() LocalCache$Segment.get() takes over 80% of the time:

profiling-guava-cache.jpg

Bear in mind, this is only a lookup of an already present bitmap. There will never happen a load in get(). I figured there would be a bookkeeping overhead in get() for the LRU queue that decides which eviction takes place if the segment is full. But this is at least an order of magnitude slower of what a Key-Lookup in LRU-LinkedHashmap.get() would give me.

We use a cache to get fast lookups if an element is in the cache, if the lookup is slow, there is no point in caching it. I also tried getAllPresent(a) and asMap() but it gives equal performance.

Library version is: guava-11.0.1.jar

LoadingCache is defined as follows:

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

My questions are:

  • Do I use it wrong?
  • Is its implementation unsuitable for Android?
  • Did i miss a configuration option?
  • Is this a known issue with the Cache that's being worked on?

Update:

After about 100 frames painted the CacheStats are:

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

After that missCount stays basicly the same as hitCount increments. In this case the cache is big enough for loads to happen sparsely, but getIfPresent() is slow nontheless.

like image 240
user643011 Avatar asked Feb 14 '12 19:02

user643011


People also ask

Is Guava cache blocking?

A non-blocking cache implementation. Guava java library has an interface LoadingCache that has methods related with cache. The library also provides a CacheBuilder whose constructor needs a CacheLoader that has different methods to load values into the cache.

Is Guava cache in memory?

Guava Cache is an in-memory cache used in applications. The following operations can be performed on a cache: Insert data.

What is Guava loading cache?

Guava provides a very powerful memory based caching mechanism by an interface LoadingCache<K,V>. Values are automatically loaded in the cache and it provides many utility methods useful for caching needs.

Where is Guava cache stored?

Guava Caches store values in RAM.


2 Answers

CacheBuilder was designed for server-side caching, where concurrency was a primary concern. It therefore trades off single-threaded and memory overhead in exchange for better multi-threaded behavior. Android developers should use LruCache, LinkedHashMap, or similar where single-threaded performance and memory are the primary concerns. In the future there may be a concurrencyLevel=0 to indicate that a lightweight, non-concurrent cache is required.

like image 138
Ben Manes Avatar answered Sep 18 '22 14:09

Ben Manes


Android code is not always optimised the same way as it is in a JVM. What can perform very well in Java might not perform as well in Android. I suggest you write a very simple cache of your own. e.g. using LinkedHashMap.removeEldestEntry() and see how that goes.

like image 33
Peter Lawrey Avatar answered Sep 17 '22 14:09

Peter Lawrey