【问题标题】:Bad performance with Guava Cache on AndroidAndroid 上的 Guava Cache 性能不佳
【发布时间】:2012-02-14 19:35:01
【问题描述】:

我们在 Android 应用程序中为位图使用加载 Google Guava LoadingCache。在应用程序中,我正在运行一个绘图线程,它将缓存中的位图绘制到画布上。如果特定的位图不在缓存中,它不会被绘制,因此任何加载都不会阻塞绘制线程。

但是,这幅画会导致视觉卡顿,并且每秒的帧数并不是我们想要的。我把它归结为缓存的getIfPresent() 方法。仅此一项就占用了应用程序总 CPU 时间的 20% 以上。在getIfPresent()LocalCache$Segment.get() 中占据了 80% 的时间:

请记住,这只是对已经存在的位图的查找。 get() 永远不会发生负载。我认为 LRU 队列在get() 中会有记账开销,该队列决定在段已满时进行哪个驱逐。但这至少比LRU-LinkedHashmap.get() 中的Key-Lookup 给我的速度慢一个数量级。

如果一个元素在缓存中,我们使用缓存来快速查找,如果查找速度很慢,缓存它就没有意义。我也尝试过getAllPresent(a)asMap(),但性能相同。

库版本为:guava-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 吗?
  • 我错过了配置选项吗?
  • 这是正在处理的缓存的已知问题吗?

更新:

绘制大约 100 帧后,CacheStats 是:

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

之后missCounthitCount 的增量基本相同。在这种情况下,缓存足够大,可以稀疏地发生负载,但getIfPresent() 仍然很慢。

【问题讨论】:

  • 请不要将其他短语加粗;它很难阅读,所以我提交了一个编辑以将其删除。
  • 感谢 simchona 对其进行编辑以使其更具可读性。
  • 你能把tiles.cacheStats()的结果贴出来吗?
  • 我想知道这是否真的是由现在修复的issue 1055引起的。
  • @LouisWasserman 你能查出这个问题是由于错误还是使用 LRU 缓存或 LinkedHashMap 更好吗?

标签: java android guava


【解决方案1】:

CacheBuilder 专为服务器端缓存而设计,其中并发性是主要问题。因此,它牺牲了单线程和内存开销,以换取更好的多线程行为。 Android 开发人员应该使用LruCacheLinkedHashMap 或类似的方法,其中单线程性能和内存是主要关注点。将来可能会有 concurrencyLevel=0 表示需要轻量级的非并发缓存。

【讨论】:

  • 我试过concurrencyLevel=1,但并没有提高访问时间。 concurrencyLevel=0 会导致错误。实际上,AtomicReferenceArray 占用了大量的 CPU 时间。我现在会选择 android.util.LruCache。感谢您对 ARM 架构的评论和您的详细回答。我希望将来能看到 concurrencyLevel=0。
  • concurrencyLevel=1 表示单个作者,多个读者。这并没有提供太多的简化工作。级别 0 表示读取器与写入同步,因此可以实现同步版本。当我对 LruCache 进行代码审查时,我们仍在处理 Cache 接口,因此将 concurrencyLevel=0 添加到 MapMaker 是低优先级。于是,LruCache 诞生了。
  • 仅供参考,LruCache 也可以在兼容性库中使用 pre-Honeycomb。 developer.android.com/sdk/compatibility-library.html
【解决方案2】:

Android 代码的优化方式并不总是与在 JVM 中相同。在 Java 中表现出色的东西在 Android 中可能表现不佳。我建议你自己编写一个非常简单的缓存。例如使用LinkedHashMap.removeEldestEntry() 看看情况如何。

【讨论】:

    猜你喜欢
    • 2015-08-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-04-20
    相关资源
    最近更新 更多