UnrefedPooledCache.java revision 93a35b93dc582e38ff8ee5979754a16b4bf4da0c
1/* 2 * Copyright (C) 2013 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.bitmap; 18 19import com.android.bitmap.util.Trace; 20 21import android.util.Log; 22import android.util.LruCache; 23import java.util.LinkedHashMap; 24import java.util.Map; 25import java.util.concurrent.LinkedBlockingQueue; 26 27/** 28 * An alternative implementation of a pool+cache. This implementation only counts 29 * unreferenced objects in its size calculation. Internally, it never evicts from 30 * its cache, and instead {@link #poll()} is allowed to return unreferenced cache 31 * entries. 32 * <p> 33 * You would only use this kind of cache if your objects are interchangeable and 34 * have significant allocation cost, and if your memory footprint is somewhat 35 * flexible. 36 * <p> 37 * Because this class only counts unreferenced objects toward targetSize, 38 * it will have a total memory footprint of: 39 * <code>(targetSize) + (# of threads concurrently writing to cache) + 40 * (total size of still-referenced entries)</code> 41 * 42 */ 43public class UnrefedPooledCache<K, V extends Poolable> implements PooledCache<K, V> { 44 45 private final LinkedHashMap<K, V> mCache; 46 private final LinkedBlockingQueue<V> mPool; 47 private final int mTargetSize; 48 private final LruCache<K, V> mNonPooledCache; 49 50 private static final boolean DEBUG = false; 51 private static final String TAG = UnrefedPooledCache.class.getSimpleName(); 52 53 /** 54 * @param targetSize not exactly a max size in practice 55 * @param nonPooledFraction the fractional portion in the range [0.0,1.0] of targetSize to 56 * dedicate to non-poolable entries 57 */ 58 public UnrefedPooledCache(int targetSize, float nonPooledFraction) { 59 mCache = new LinkedHashMap<K, V>(0, 0.75f, true); 60 mPool = new LinkedBlockingQueue<V>(); 61 final int nonPooledSize = Math.round(targetSize * nonPooledFraction); 62 if (nonPooledSize > 0) { 63 mNonPooledCache = new NonPooledCache(nonPooledSize); 64 } else { 65 mNonPooledCache = null; 66 } 67 mTargetSize = targetSize - nonPooledSize; 68 } 69 70 @Override 71 public V get(K key, boolean incrementRefCount) { 72 Trace.beginSection("cache get"); 73 synchronized (mCache) { 74 V result = mCache.get(key); 75 if (result == null && mNonPooledCache != null) { 76 result = mNonPooledCache.get(key); 77 } 78 if (incrementRefCount && result != null) { 79 result.acquireReference(); 80 } 81 Trace.endSection(); 82 return result; 83 } 84 } 85 86 @Override 87 public V put(K key, V value) { 88 Trace.beginSection("cache put"); 89 synchronized (mCache) { 90 final V prev; 91 if (value.isEligibleForPooling()) { 92 prev = mCache.put(key, value); 93 } else if (mNonPooledCache != null) { 94 prev = mNonPooledCache.put(key, value); 95 } else { 96 prev = null; 97 } 98 Trace.endSection(); 99 return prev; 100 } 101 } 102 103 @Override 104 public void offer(V value) { 105 Trace.beginSection("pool offer"); 106 if (value.getRefCount() != 0 || !value.isEligibleForPooling()) { 107 throw new IllegalArgumentException("unexpected offer of an invalid object: " + value); 108 } 109 mPool.offer(value); 110 Trace.endSection(); 111 } 112 113 @Override 114 public V poll() { 115 Trace.beginSection("pool poll"); 116 final V pooled = mPool.poll(); 117 if (pooled != null) { 118 Trace.endSection(); 119 return pooled; 120 } 121 122 synchronized (mCache) { 123 int unrefSize = 0; 124 Map.Entry<K, V> eldestUnref = null; 125 for (Map.Entry<K, V> entry : mCache.entrySet()) { 126 final V value = entry.getValue(); 127 if (value.getRefCount() > 0 || !value.isEligibleForPooling()) { 128 continue; 129 } 130 if (eldestUnref == null) { 131 eldestUnref = entry; 132 } 133 unrefSize += sizeOf(value); 134 if (unrefSize > mTargetSize) { 135 break; 136 } 137 } 138 // only return a scavenged cache entry if the cache has enough 139 // eligible (unreferenced) items 140 if (unrefSize <= mTargetSize) { 141 if (DEBUG) { 142 Log.e(TAG, "POOL SCAVENGE FAILED, cache not fully warm yet. szDelta=" 143 + (mTargetSize-unrefSize)); 144 } 145 Trace.endSection(); 146 return null; 147 } else { 148 mCache.remove(eldestUnref.getKey()); 149 if (DEBUG) { 150 Log.e(TAG, "POOL SCAVENGE SUCCESS, oldKey=" + eldestUnref.getKey()); 151 } 152 Trace.endSection(); 153 return eldestUnref.getValue(); 154 } 155 } 156 } 157 158 protected int sizeOf(V value) { 159 return 1; 160 } 161 162 @Override 163 public String toDebugString() { 164 if (DEBUG) { 165 final StringBuilder sb = new StringBuilder("["); 166 sb.append(super.toString()); 167 int size = 0; 168 synchronized (mCache) { 169 sb.append(" poolCount="); 170 sb.append(mPool.size()); 171 sb.append(" cacheSize="); 172 sb.append(mCache.size()); 173 if (mNonPooledCache != null) { 174 sb.append(" nonPooledCacheSize="); 175 sb.append(mNonPooledCache.size()); 176 } 177 sb.append("\n---------------------"); 178 for (V val : mPool) { 179 size += sizeOf(val); 180 sb.append("\n\tpool item: "); 181 sb.append(val); 182 } 183 sb.append("\n---------------------"); 184 for (Map.Entry<K, V> item : mCache.entrySet()) { 185 final V val = item.getValue(); 186 sb.append("\n\tcache key="); 187 sb.append(item.getKey()); 188 sb.append(" val="); 189 sb.append(val); 190 size += sizeOf(val); 191 } 192 sb.append("\n---------------------"); 193 if (mNonPooledCache != null) { 194 for (Map.Entry<K, V> item : mNonPooledCache.snapshot().entrySet()) { 195 final V val = item.getValue(); 196 sb.append("\n\tnon-pooled cache key="); 197 sb.append(item.getKey()); 198 sb.append(" val="); 199 sb.append(val); 200 size += sizeOf(val); 201 } 202 sb.append("\n---------------------"); 203 } 204 sb.append("\nTOTAL SIZE=" + size); 205 } 206 sb.append("]"); 207 return sb.toString(); 208 } else { 209 return null; 210 } 211 } 212 213 private class NonPooledCache extends LruCache<K, V> { 214 215 public NonPooledCache(int maxSize) { 216 super(maxSize); 217 } 218 219 @Override 220 protected int sizeOf(K key, V value) { 221 return UnrefedPooledCache.this.sizeOf(value); 222 } 223 224 } 225 226 @Override 227 public void clear() { 228 mCache.clear(); 229 mPool.clear(); 230 } 231} 232