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