UnrefedPooledCache.java revision cea0c012d538f11b3ee97d4b7e78f4c1ea73d5be
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 = DecodeTask.DEBUG;
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