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