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