Плохая производительность с Guava Cache на Android

Мы используем загрузку Google Guava LoadCache для растровых изображений в приложении для Android. В приложении я запускаю рисунок Thread, который рисует растровые изображения в кеше на Canvas. Если конкретное растровое изображение отсутствует в кеше, оно не получается нарисованным, так что никакая загрузка никогда не блокирует рисование Thread.

Тем не менее, картина приводит к визуальному заиканию, а скорость кадров в секунду - это не то, как мы хотели бы. Я прибил его к getIfPresent() кеша. Только это занимает более 20% от общего времени процессора. В getIfPresent() LocalCache$Segment.get() занимает более 80% времени:

profiling-guava-cache.jpg

Имейте в виду, что это всего лишь просмотр уже существующего растрового изображения. В get() никогда не будет нагрузки. Я полагал, что в get() для очереди LRU будет выполняться служебная операция, которая решает, какое выселение происходит, если сегмент заполнен. Но это по крайней мере на порядок медленнее того, что Key-Lookup в LRU-LinkedHashmap.get() даст мне.

Мы используем кеш для быстрого поиска, если элемент находится в кеше, если поиск медленный, нет смысла его кэшировать. Я также попробовал getAllPresent(a) и asMap() но он дает равную производительность.

Версия библиотеки: guava-11.0.1.jar

LoadCache определяется следующим образом:

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

Мои вопросы:

  • Я использую это неправильно?
  • Является ли она неприменимой для Android?
  • Я пропустил вариант конфигурации?
  • Это известная проблема с работающим кэшем?

Обновить:

После примерно 100 кадров, нарисованных CacheStats:

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

После этого missCount остается в основном таким же, как приращения hitCount. В этом случае кеш достаточно велик, чтобы нагрузки случались редко, но getIfPresent медленнее.

Ответ 1

CacheBuilder был разработан для кеширования на стороне сервера, где concurrency была главной проблемой. Поэтому он обменивается однопоточными и служебными данными памяти в обмен на лучшее многопоточное поведение. Разработчики Android должны использовать LruCache, LinkedHashMap или подобные, где важны однопоточная производительность и память. В будущем может быть concurrencyLevel = 0, чтобы указать, что требуется легкий, неконкурентный кеш.

Ответ 2

Код Android не всегда оптимизирован так же, как и в JVM. То, что может хорошо работать в Java, может не работать в Android. Я предлагаю вам написать очень простой кэш. например используя LinkedHashMap.removeEldestEntry() и посмотрите, как это происходит.