UnrefedPooledCache.java revision a07e0af0f1997ce3d40df6a8a9f44cb0b2e4c07f
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 return null; 93 } 94 synchronized (mCache) { 95 final V prev; 96 if (value.isEligibleForPooling()) { 97 prev = mCache.put(key, value); 98 } else if (mNonPooledCache != null) { 99 prev = mNonPooledCache.put(key, value); 100 } else { 101 prev = null; 102 } 103 Trace.endSection(); 104 return prev; 105 } 106 } 107 108 @Override 109 public void offer(V value) { 110 Trace.beginSection("pool offer"); 111 if (value.getRefCount() != 0 || !value.isEligibleForPooling()) { 112 throw new IllegalArgumentException("unexpected offer of an invalid object: " + value); 113 } 114 mPool.offer(value); 115 Trace.endSection(); 116 } 117 118 @Override 119 public V poll() { 120 Trace.beginSection("pool poll"); 121 final V pooled = mPool.poll(); 122 if (pooled != null) { 123 Trace.endSection(); 124 return pooled; 125 } 126 127 synchronized (mCache) { 128 int unrefSize = 0; 129 Map.Entry<K, V> eldestUnref = null; 130 for (Map.Entry<K, V> entry : mCache.entrySet()) { 131 final V value = entry.getValue(); 132 if (value.getRefCount() > 0 || !value.isEligibleForPooling()) { 133 continue; 134 } 135 if (eldestUnref == null) { 136 eldestUnref = entry; 137 } 138 unrefSize += sizeOf(value); 139 if (unrefSize > mTargetSize) { 140 break; 141 } 142 } 143 // only return a scavenged cache entry if the cache has enough 144 // eligible (unreferenced) items 145 if (unrefSize <= mTargetSize) { 146 if (DEBUG) { 147 Log.e(TAG, "POOL SCAVENGE FAILED, cache not fully warm yet. szDelta=" 148 + (mTargetSize-unrefSize)); 149 } 150 Trace.endSection(); 151 return null; 152 } else { 153 mCache.remove(eldestUnref.getKey()); 154 if (DEBUG) { 155 Log.e(TAG, "POOL SCAVENGE SUCCESS, oldKey=" + eldestUnref.getKey()); 156 } 157 Trace.endSection(); 158 return eldestUnref.getValue(); 159 } 160 } 161 } 162 163 protected int sizeOf(V value) { 164 return 1; 165 } 166 167 @Override 168 public String toDebugString() { 169 if (DEBUG) { 170 final StringBuilder sb = new StringBuilder("["); 171 sb.append(super.toString()); 172 int size = 0; 173 synchronized (mCache) { 174 sb.append(" poolCount="); 175 sb.append(mPool.size()); 176 sb.append(" cacheSize="); 177 sb.append(mCache.size()); 178 if (mNonPooledCache != null) { 179 sb.append(" nonPooledCacheSize="); 180 sb.append(mNonPooledCache.size()); 181 } 182 sb.append("\n---------------------"); 183 for (V val : mPool) { 184 size += sizeOf(val); 185 sb.append("\n\tpool item: "); 186 sb.append(val); 187 } 188 sb.append("\n---------------------"); 189 for (Map.Entry<K, V> item : mCache.entrySet()) { 190 final V val = item.getValue(); 191 sb.append("\n\tcache key="); 192 sb.append(item.getKey()); 193 sb.append(" val="); 194 sb.append(val); 195 size += sizeOf(val); 196 } 197 sb.append("\n---------------------"); 198 if (mNonPooledCache != null) { 199 for (Map.Entry<K, V> item : mNonPooledCache.snapshot().entrySet()) { 200 final V val = item.getValue(); 201 sb.append("\n\tnon-pooled cache key="); 202 sb.append(item.getKey()); 203 sb.append(" val="); 204 sb.append(val); 205 size += sizeOf(val); 206 } 207 sb.append("\n---------------------"); 208 } 209 sb.append("\nTOTAL SIZE=" + size); 210 } 211 sb.append("]"); 212 return sb.toString(); 213 } else { 214 return null; 215 } 216 } 217 218 private class NonPooledCache extends LruCache<K, V> { 219 220 public NonPooledCache(int maxSize) { 221 super(maxSize); 222 } 223 224 @Override 225 protected int sizeOf(K key, V value) { 226 return UnrefedPooledCache.this.sizeOf(value); 227 } 228 229 } 230 231 @Override 232 public void clear() { 233 mCache.clear(); 234 mPool.clear(); 235 } 236} 237