Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qcache3q_p.h
Go to the documentation of this file.
1// Copyright (C) 2021 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#ifndef QCACHE3Q_H
5#define QCACHE3Q_H
6
7//
8// W A R N I N G
9// -------------
10//
11// This file is not part of the Qt API. It exists purely as an
12// implementation detail. This header file may change from version to
13// version without notice, or even be removed.
14//
15// We mean it.
16
17#include <QtCore/QSharedPointer>
18#include <QtCore/QDebug>
19
20QT_BEGIN_NAMESPACE
21
22template <class Key, class T>
23class QCache3QDefaultEvictionPolicy
24{
25protected:
26 /* called just before a key/value pair is about to be _evicted_ */
27 void aboutToBeEvicted(const Key &key, QSharedPointer<T> obj);
28 /* called just before a key/value pair is about to be removed, by
29 * clear(), remove() or by the destructor (which calls clear) */
30 void aboutToBeRemoved(const Key &key, QSharedPointer<T> obj);
31};
32
33template <class Key, class T>
34void QCache3QDefaultEvictionPolicy<Key,T>::aboutToBeEvicted(const Key &key, QSharedPointer<T> obj)
35{
36 Q_UNUSED(key);
37 Q_UNUSED(obj);
38}
39
40template <class Key, class T>
41void QCache3QDefaultEvictionPolicy<Key,T>::aboutToBeRemoved(const Key &key, QSharedPointer<T> obj)
42{
43 Q_UNUSED(key);
44 Q_UNUSED(obj);
45}
46
47/*
48 * QCache3Q
49 *
50 * A cache template class for managing QSharedPointers to objects with an
51 * associated key. It's a lot like QCache, but uses an alternative algorithm
52 * '3Q' -- which is the '2Q' algorithm plus an extra queue for previously popular
53 * but evicted nodes, and a 'ghost' list of recent evictions to make a better
54 * placement choice if they are requested again.
55 *
56 * New nodes enter the cache on the "newbies" queue, which is evicted LRA
57 * (least-recently-added). If a newbie is popular enough (it has been requested
58 * more than promoteAt times), it will be promoted to a "regular". Regulars
59 * are evicted LRU (least-recently-used). If a regular is under consideration
60 * for eviction, its popularity is compared to the mean popularity of the whole
61 * regulars queue. If it is greater, it is instead moved to the "hobos" queue.
62 * The "hobos" queue is also evicted LRU, but has a maximum size constraint
63 * so eviction from it is less likely than from the regulars.
64 *
65 * Tweakables:
66 * * maxCost = maximum total cost for the whole cache
67 * * minRecent = minimum size that q1 ("newbies") has to be before eviction
68 * from it takes place
69 * * maxOldPopular = maximum size that q3 ("hobos") can reach before eviction
70 * from it takes place
71 * * promoteAt = minimum popularity necessary to promote a node from
72 * "newbie" to "regular"
73 */
74template <class Key, class T, class EvPolicy = QCache3QDefaultEvictionPolicy<Key,T> >
75class QCache3Q : public EvPolicy
76{
78private:
79 class Queue;
80 class Node
81 {
82 public:
83 inline explicit Node() : q(0), n(0), p(0), pop(0), cost(0) {}
84
85 Queue *q;
86 Node *n;
87 Node *p;
88 Key k;
89 QSharedPointer<T> v;
90 quint64 pop; // popularity, incremented each ping
91 int cost;
92 };
93
94 class Queue
95 {
96 public:
97 inline explicit Queue() : f(0), l(0), cost(0), pop(0), size(0) {}
98
99 Node *f;
100 Node *l;
101 int cost; // total cost of nodes on the queue
102 quint64 pop; // sum of popularity values on the queue
103 int size; // size of the queue
104 };
105
106 Queue *q1_; // "newbies": seen only once, evicted LRA (least-recently-added)
107 Queue *q2_; // regular nodes, promoted from newbies, evicted LRU
108 Queue *q3_; // "hobos": evicted from q2 but were very popular (above mean)
109 Queue *q1_evicted_; // ghosts of recently evicted newbies and regulars
110 QHash<Key, Node *> lookup_;
111
112public:
113 explicit QCache3Q(int maxCost = 0, int minRecent = -1, int maxOldPopular = -1);
114 inline ~QCache3Q() { clear(); delete q1_; delete q2_; delete q3_; delete q1_evicted_; }
115
116 inline int maxCost() const { return maxCost_; }
117 void setMaxCost(int maxCost, int minRecent = -1, int maxOldPopular = -1);
118
119 inline int promoteAt() const { return promote_; }
120 inline void setPromoteAt(int p) { promote_ = p; }
121
122 inline int totalCost() const { return q1_->cost + q2_->cost + q3_->cost; }
123
124 void clear();
125 bool insert(const Key &key, QSharedPointer<T> object, int cost = 1);
126 QSharedPointer<T> object(const Key &key) const;
127 QSharedPointer<T> operator[](const Key &key) const;
128
129 void remove(const Key &key, bool force = false);
130 QList<Key> keys() const;
132
133 // Copy data directly into a queue. Designed for single use after construction
134 void deserializeQueue(int queueNumber, const QList<Key> &keys,
135 const QList<QSharedPointer<T> > &values, const QList<int> &costs);
136 // Copy data from specific queue into list
137 void serializeQueue(int queueNumber, QList<QSharedPointer<T> > &buffer);
138
139private:
140 int maxCost_, minRecent_, maxOldPopular_;
141 int hitCount_, missCount_, promote_;
142
143 void rebalance();
144 void unlink(Node *n);
145 void link_front(Node *n, Queue *q);
146};
147
148template <class Key, class T, class EvPolicy>
149void QCache3Q<Key,T,EvPolicy>::printStats()
150{
151 qDebug("\n=== cache %p ===", this);
152 qDebug("hits: %d (%.2f%%)\tmisses: %d\tfill: %.2f%%", hitCount_,
153 100.0 * float(hitCount_) / (float(hitCount_ + missCount_)),
154 missCount_,
155 100.0 * float(totalCost()) / float(maxCost()));
156 qDebug("q1g: size=%d, pop=%llu", q1_evicted_->size, q1_evicted_->pop);
157 qDebug("q1: cost=%d, size=%d, pop=%llu", q1_->cost, q1_->size, q1_->pop);
158 qDebug("q2: cost=%d, size=%d, pop=%llu", q2_->cost, q2_->size, q2_->pop);
159 qDebug("q3: cost=%d, size=%d, pop=%llu", q3_->cost, q3_->size, q3_->pop);
160}
161
162template <class Key, class T, class EvPolicy>
163QCache3Q<Key,T,EvPolicy>::QCache3Q(int maxCost, int minRecent, int maxOldPopular)
164 : q1_(new Queue), q2_(new Queue), q3_(new Queue), q1_evicted_(new Queue),
165 maxCost_(maxCost), minRecent_(minRecent), maxOldPopular_(maxOldPopular),
166 hitCount_(0), missCount_(0), promote_(0)
167{
168 if (minRecent_ < 0)
169 minRecent_ = maxCost_ / 3;
170 if (maxOldPopular_ < 0)
171 maxOldPopular_ = maxCost_ / 5;
172}
173
174template <class Key, class T, class EvPolicy>
175void QCache3Q<Key,T,EvPolicy>::serializeQueue(int queueNumber, QList<QSharedPointer<T> > &buffer)
176{
177 Q_ASSERT(queueNumber >= 1 && queueNumber <= 4);
178 Queue *queue = queueNumber == 1 ? q1_ :
179 queueNumber == 2 ? q2_ :
180 queueNumber == 3 ? q3_ :
181 q1_evicted_;
182 for (Node *node = queue->f; node; node = node->n)
183 buffer.append(node->v);
184}
185
186template <class Key, class T, class EvPolicy>
187void QCache3Q<Key,T,EvPolicy>::deserializeQueue(int queueNumber, const QList<Key> &keys,
188 const QList<QSharedPointer<T> > &values, const QList<int> &costs)
189{
190 Q_ASSERT(queueNumber >= 1 && queueNumber <= 4);
191 int bufferSize = keys.size();
192 if (bufferSize == 0)
193 return;
194 clear();
195 Queue *queue = queueNumber == 1 ? q1_ :
196 queueNumber == 2 ? q2_ :
197 queueNumber == 3 ? q3_ :
198 q1_evicted_;
199 for (int i = 0; i<bufferSize; ++i) {
200 Node *node = new Node;
201 node->v = values[i];
202 node->k = keys[i];
203 node->cost = costs[i];
204 link_front(node, queue);
205 lookup_[keys[i]] = node;
206 }
207}
208
209
210template <class Key, class T, class EvPolicy>
211inline void QCache3Q<Key,T,EvPolicy>::setMaxCost(int maxCost, int minRecent, int maxOldPopular)
212{
213 maxCost_ = maxCost;
214 minRecent_ = minRecent;
215 maxOldPopular_ = maxOldPopular;
216 if (minRecent_ < 0)
217 minRecent_ = maxCost_ / 3;
218 if (maxOldPopular_ < 0)
219 maxOldPopular_ = maxCost_ / 5;
220 rebalance();
221}
222
223template <class Key, class T, class EvPolicy>
224bool QCache3Q<Key,T,EvPolicy>::insert(const Key &key, QSharedPointer<T> object, int cost)
225{
226 if (cost > maxCost_) {
227 return false;
228 }
229
230 [[maybe_unused]] const auto oldSize = lookup_.size();
231 Node* &n = lookup_[key];
232 Q_ASSERT(bool(n) == (lookup_.size() == oldSize)); // either a new nullptr, or existing non-null
233
234 if (n) {
235 n->v = object;
236 n->q->cost -= n->cost;
237 n->cost = cost;
238 n->q->cost += cost;
239
240 if (n->q == q1_evicted_) {
241 if (n->pop > (uint)promote_) {
242 unlink(n);
243 link_front(n, q2_);
244 rebalance();
245 }
246 } else if (n->q != q1_) {
247 Queue *q = n->q;
248 unlink(n);
249 link_front(n, q);
250 rebalance();
251 }
252
253 return true;
254 }
255
256 n = new Node;
257 n->v = object;
258 n->k = key;
259 n->cost = cost;
260 link_front(n, q1_);
261
262 rebalance();
263
264 return true;
265}
266
267template <class Key, class T, class EvPolicy>
268void QCache3Q<Key,T,EvPolicy>::clear()
269{
270 while (q1_evicted_->f) {
271 Node *n = q1_evicted_->f;
272 unlink(n);
273 delete n;
274 }
275
276 while (q1_->f) {
277 Node *n = q1_->f;
278 unlink(n);
279 EvPolicy::aboutToBeRemoved(n->k, n->v);
280 delete n;
281 }
282
283 while (q2_->f) {
284 Node *n = q2_->f;
285 unlink(n);
286 EvPolicy::aboutToBeRemoved(n->k, n->v);
287 delete n;
288 }
289
290 while (q3_->f) {
291 Node *n = q3_->f;
292 unlink(n);
293 EvPolicy::aboutToBeRemoved(n->k, n->v);
294 delete n;
295 }
296
297 lookup_.clear();
298}
299
300template <class Key, class T, class EvPolicy>
301void QCache3Q<Key,T,EvPolicy>::unlink(Node *n)
302{
303 if (n->n)
304 n->n->p = n->p;
305 if (n->p)
306 n->p->n = n->n;
307 if (n->q->f == n)
308 n->q->f = n->n;
309 if (n->q->l == n)
310 n->q->l = n->p;
311 n->n = 0;
312 n->p = 0;
313 n->q->pop -= n->pop;
314 n->q->cost -= n->cost;
315 n->q->size--;
316 n->q = 0;
317}
318
319template <class Key, class T, class EvPolicy>
320void QCache3Q<Key,T,EvPolicy>::link_front(Node *n, Queue *q)
321{
322 n->n = q->f;
323 n->p = 0;
324 n->q = q;
325 if (q->f)
326 q->f->p = n;
327 q->f = n;
328 if (!q->l)
329 q->l = n;
330
331 q->pop += n->pop;
332 q->cost += n->cost;
333 q->size++;
334}
335
336template <class Key, class T, class EvPolicy>
337void QCache3Q<Key,T,EvPolicy>::rebalance()
338{
339 while (q1_evicted_->size > (q1_->size + q2_->size + q3_->size) * 4) {
340 Node *n = q1_evicted_->l;
341 unlink(n);
342 lookup_.remove(n->k);
343 delete n;
344 }
345
346 while ((q1_->cost + q2_->cost + q3_->cost) > maxCost_) {
347 if (q3_->cost > maxOldPopular_) {
348 Node *n = q3_->l;
349 unlink(n);
350 EvPolicy::aboutToBeEvicted(n->k, n->v);
351 lookup_.remove(n->k);
352 delete n;
353 } else if (q1_->cost > minRecent_) {
354 Node *n = q1_->l;
355 unlink(n);
356 EvPolicy::aboutToBeEvicted(n->k, n->v);
357 n->v.clear();
358 n->cost = 0;
359 link_front(n, q1_evicted_);
360 } else {
361 Node *n = q2_->l;
362 unlink(n);
363 if (q2_->size && n->pop > (q2_->pop / q2_->size)) {
364 link_front(n, q3_);
365 } else {
366 EvPolicy::aboutToBeEvicted(n->k, n->v);
367 n->v.clear();
368 n->cost = 0;
369 link_front(n, q1_evicted_);
370 }
371 }
372 }
373}
374
375template <class Key, class T, class EvPolicy>
376void QCache3Q<Key,T,EvPolicy>::remove(const Key &key, bool force)
377{
378 const auto it = std::as_const(lookup_).find(key);
379 if (it == lookup_.cend())
380 return;
381
382 Node *n = *it;
383 unlink(n);
384 if (n->q != q1_evicted_ && !force)
385 EvPolicy::aboutToBeRemoved(n->k, n->v);
386 lookup_.erase(it);
387 delete n;
388}
389
390template <class Key, class T, class EvPolicy>
391QList<Key> QCache3Q<Key,T,EvPolicy>::keys() const
392{
393 return lookup_.keys();
394}
395
396template <class Key, class T, class EvPolicy>
397QSharedPointer<T> QCache3Q<Key,T,EvPolicy>::object(const Key &key) const
398{
399 if (!lookup_.contains(key)) {
400 const_cast<QCache3Q<Key,T,EvPolicy> *>(this)->missCount_++;
401 return QSharedPointer<T>();
402 }
403
404 QCache3Q<Key,T,EvPolicy> *me = const_cast<QCache3Q<Key,T,EvPolicy> *>(this);
405
406 Node *n = me->lookup_[key];
407 n->pop++;
408 n->q->pop++;
409
410 if (n->q == q1_) {
411 me->hitCount_++;
412
413 if (n->pop > (quint64)promote_) {
414 me->unlink(n);
415 me->link_front(n, q2_);
416 me->rebalance();
417 }
418 } else if (n->q != q1_evicted_) {
419 me->hitCount_++;
420
421 Queue *q = n->q;
422 me->unlink(n);
423 me->link_front(n, q);
424 me->rebalance();
425 } else {
426 me->missCount_++;
427 }
428
429 return n->v;
430}
431
432template <class Key, class T, class EvPolicy>
433inline QSharedPointer<T> QCache3Q<Key,T,EvPolicy>::operator[](const Key &key) const
434{
435 return object(key);
436}
437
438QT_END_NAMESPACE
439
440#endif // QCACHE3Q_H
int totalCost() const
Definition qcache3q_p.h:122
void clear()
Definition qcache3q_p.h:268
QSharedPointer< T > operator[](const Key &key) const
Definition qcache3q_p.h:433
int maxCost() const
Definition qcache3q_p.h:116
QCache3Q(int maxCost=0, int minRecent=-1, int maxOldPopular=-1)
Definition qcache3q_p.h:163
void deserializeQueue(int queueNumber, const QList< Key > &keys, const QList< QSharedPointer< T > > &values, const QList< int > &costs)
Definition qcache3q_p.h:187
bool insert(const Key &key, QSharedPointer< T > object, int cost=1)
Definition qcache3q_p.h:224
void remove(const Key &key, bool force=false)
Definition qcache3q_p.h:376
QSharedPointer< T > object(const Key &key) const
Definition qcache3q_p.h:397
void setMaxCost(int maxCost, int minRecent=-1, int maxOldPopular=-1)
Definition qcache3q_p.h:211
void printStats()
Definition qcache3q_p.h:149
int promoteAt() const
Definition qcache3q_p.h:119
QList< Key > keys() const
Definition qcache3q_p.h:391
void setPromoteAt(int p)
Definition qcache3q_p.h:120
void serializeQueue(int queueNumber, QList< QSharedPointer< T > > &buffer)
Definition qcache3q_p.h:175
QGeoFileTileCache * cache
QGeoFileTileCache * cache