summaryrefslogtreecommitdiff
path: root/src/com/android/bitmap/UnrefedPooledCache.java
diff options
context:
space:
mode:
authorSam Blitzstein <sblitz@google.com>2013-10-09 14:11:27 -0700
committerSam Blitzstein <sblitz@google.com>2013-10-15 17:34:58 -0700
commit93a35b93dc582e38ff8ee5979754a16b4bf4da0c (patch)
tree9034ab3155e8781b0cd77fb70882f911080f6f89 /src/com/android/bitmap/UnrefedPooledCache.java
parentce2b0fdc1e9c9d083faab75b6bdfbea27bf574e2 (diff)
downloadbitmap-93a35b93dc582e38ff8ee5979754a16b4bf4da0c.tar.gz
Initial commit from Gmail's Cache system.
Change-Id: I14168ab3bc02b77399a1812f62bd77ac797232c5
Diffstat (limited to 'src/com/android/bitmap/UnrefedPooledCache.java')
-rw-r--r--src/com/android/bitmap/UnrefedPooledCache.java231
1 files changed, 231 insertions, 0 deletions
diff --git a/src/com/android/bitmap/UnrefedPooledCache.java b/src/com/android/bitmap/UnrefedPooledCache.java
new file mode 100644
index 0000000..ff0ab7b
--- /dev/null
+++ b/src/com/android/bitmap/UnrefedPooledCache.java
@@ -0,0 +1,231 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bitmap;
+
+import com.android.bitmap.util.Trace;
+
+import android.util.Log;
+import android.util.LruCache;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.concurrent.LinkedBlockingQueue;
+
+/**
+ * An alternative implementation of a pool+cache. This implementation only counts
+ * unreferenced objects in its size calculation. Internally, it never evicts from
+ * its cache, and instead {@link #poll()} is allowed to return unreferenced cache
+ * entries.
+ * <p>
+ * You would only use this kind of cache if your objects are interchangeable and
+ * have significant allocation cost, and if your memory footprint is somewhat
+ * flexible.
+ * <p>
+ * Because this class only counts unreferenced objects toward targetSize,
+ * it will have a total memory footprint of:
+ * <code>(targetSize) + (# of threads concurrently writing to cache) +
+ * (total size of still-referenced entries)</code>
+ *
+ */
+public class UnrefedPooledCache<K, V extends Poolable> implements PooledCache<K, V> {
+
+ private final LinkedHashMap<K, V> mCache;
+ private final LinkedBlockingQueue<V> mPool;
+ private final int mTargetSize;
+ private final LruCache<K, V> mNonPooledCache;
+
+ private static final boolean DEBUG = false;
+ private static final String TAG = UnrefedPooledCache.class.getSimpleName();
+
+ /**
+ * @param targetSize not exactly a max size in practice
+ * @param nonPooledFraction the fractional portion in the range [0.0,1.0] of targetSize to
+ * dedicate to non-poolable entries
+ */
+ public UnrefedPooledCache(int targetSize, float nonPooledFraction) {
+ mCache = new LinkedHashMap<K, V>(0, 0.75f, true);
+ mPool = new LinkedBlockingQueue<V>();
+ final int nonPooledSize = Math.round(targetSize * nonPooledFraction);
+ if (nonPooledSize > 0) {
+ mNonPooledCache = new NonPooledCache(nonPooledSize);
+ } else {
+ mNonPooledCache = null;
+ }
+ mTargetSize = targetSize - nonPooledSize;
+ }
+
+ @Override
+ public V get(K key, boolean incrementRefCount) {
+ Trace.beginSection("cache get");
+ synchronized (mCache) {
+ V result = mCache.get(key);
+ if (result == null && mNonPooledCache != null) {
+ result = mNonPooledCache.get(key);
+ }
+ if (incrementRefCount && result != null) {
+ result.acquireReference();
+ }
+ Trace.endSection();
+ return result;
+ }
+ }
+
+ @Override
+ public V put(K key, V value) {
+ Trace.beginSection("cache put");
+ synchronized (mCache) {
+ final V prev;
+ if (value.isEligibleForPooling()) {
+ prev = mCache.put(key, value);
+ } else if (mNonPooledCache != null) {
+ prev = mNonPooledCache.put(key, value);
+ } else {
+ prev = null;
+ }
+ Trace.endSection();
+ return prev;
+ }
+ }
+
+ @Override
+ public void offer(V value) {
+ Trace.beginSection("pool offer");
+ if (value.getRefCount() != 0 || !value.isEligibleForPooling()) {
+ throw new IllegalArgumentException("unexpected offer of an invalid object: " + value);
+ }
+ mPool.offer(value);
+ Trace.endSection();
+ }
+
+ @Override
+ public V poll() {
+ Trace.beginSection("pool poll");
+ final V pooled = mPool.poll();
+ if (pooled != null) {
+ Trace.endSection();
+ return pooled;
+ }
+
+ synchronized (mCache) {
+ int unrefSize = 0;
+ Map.Entry<K, V> eldestUnref = null;
+ for (Map.Entry<K, V> entry : mCache.entrySet()) {
+ final V value = entry.getValue();
+ if (value.getRefCount() > 0 || !value.isEligibleForPooling()) {
+ continue;
+ }
+ if (eldestUnref == null) {
+ eldestUnref = entry;
+ }
+ unrefSize += sizeOf(value);
+ if (unrefSize > mTargetSize) {
+ break;
+ }
+ }
+ // only return a scavenged cache entry if the cache has enough
+ // eligible (unreferenced) items
+ if (unrefSize <= mTargetSize) {
+ if (DEBUG) {
+ Log.e(TAG, "POOL SCAVENGE FAILED, cache not fully warm yet. szDelta="
+ + (mTargetSize-unrefSize));
+ }
+ Trace.endSection();
+ return null;
+ } else {
+ mCache.remove(eldestUnref.getKey());
+ if (DEBUG) {
+ Log.e(TAG, "POOL SCAVENGE SUCCESS, oldKey=" + eldestUnref.getKey());
+ }
+ Trace.endSection();
+ return eldestUnref.getValue();
+ }
+ }
+ }
+
+ protected int sizeOf(V value) {
+ return 1;
+ }
+
+ @Override
+ public String toDebugString() {
+ if (DEBUG) {
+ final StringBuilder sb = new StringBuilder("[");
+ sb.append(super.toString());
+ int size = 0;
+ synchronized (mCache) {
+ sb.append(" poolCount=");
+ sb.append(mPool.size());
+ sb.append(" cacheSize=");
+ sb.append(mCache.size());
+ if (mNonPooledCache != null) {
+ sb.append(" nonPooledCacheSize=");
+ sb.append(mNonPooledCache.size());
+ }
+ sb.append("\n---------------------");
+ for (V val : mPool) {
+ size += sizeOf(val);
+ sb.append("\n\tpool item: ");
+ sb.append(val);
+ }
+ sb.append("\n---------------------");
+ for (Map.Entry<K, V> item : mCache.entrySet()) {
+ final V val = item.getValue();
+ sb.append("\n\tcache key=");
+ sb.append(item.getKey());
+ sb.append(" val=");
+ sb.append(val);
+ size += sizeOf(val);
+ }
+ sb.append("\n---------------------");
+ if (mNonPooledCache != null) {
+ for (Map.Entry<K, V> item : mNonPooledCache.snapshot().entrySet()) {
+ final V val = item.getValue();
+ sb.append("\n\tnon-pooled cache key=");
+ sb.append(item.getKey());
+ sb.append(" val=");
+ sb.append(val);
+ size += sizeOf(val);
+ }
+ sb.append("\n---------------------");
+ }
+ sb.append("\nTOTAL SIZE=" + size);
+ }
+ sb.append("]");
+ return sb.toString();
+ } else {
+ return null;
+ }
+ }
+
+ private class NonPooledCache extends LruCache<K, V> {
+
+ public NonPooledCache(int maxSize) {
+ super(maxSize);
+ }
+
+ @Override
+ protected int sizeOf(K key, V value) {
+ return UnrefedPooledCache.this.sizeOf(value);
+ }
+
+ }
+
+ @Override
+ public void clear() {
+ mCache.clear();
+ mPool.clear();
+ }
+}