2012-02-14 4 views
18

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

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

profiling-guava-cache.jpg

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

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

библиотека версия: гуавы-11.0.1.jar

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

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?
  • Я пропустил вариант конфигурации?
  • Это известная проблема с кешем, над которым работает?

Update:

После примерно 100 кадров окрашены в CacheStats являются:

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

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

+1

Пожалуйста, не смелейте любую другую фразу; это было трудно прочитать, поэтому я представил редактирование, чтобы вытащить его. – simchona

+0

Спасибо, simchona за его редактирование, чтобы сделать его более читаемым. – user643011

+0

Не могли бы вы опубликовать результаты 'tiles.cacheStats()'? –

ответ

30

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

+1

Я пробовал concurrencyLevel = 1, но это не улучшило время доступа. concurrencyLevel = 0 приводит к ошибке. Действительно, AtomicReferenceArray занимает значительное количество процессорного времени. На данный момент я поеду на android.util.LruCache. Спасибо за ваш комментарий относительно архитектуры ARM и ваш подробный ответ. Мне бы хотелось увидеть concurrencyLevel = 0 в будущем. – user643011

+1

concurrencyLevel = 1 означает единый писатель, несколько считывателей. Это не обеспечивает большую часть упрощения работы. Уровень 0 указывает, что считыватели синхронизируются с записью, поэтому может быть реализована синхронизированная версия. Когда я просматривал код LruCache, мы все еще работали над интерфейсом Cache, поэтому было невысоко приоритетно добавлять concurrencyLevel = 0 в MapMaker. Таким образом, LruCache родился. –

+4

FYI, LruCache также доступен для предварительной сотовой связи в библиотеке совместимости. http://developer.android.com/sdk/compatibility-library.html –

2

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

Смежные вопросы