Skip to content
/ qtbase Public

Commit 90b68cb

Browse files
committed
QMap: don't detach in take()
When shared, the old use used detach(), thereby copying the key (and must needs allocating a node for it) just to extract it again in the next step. While this is easy to understand, it has several problems: First, we allocate a node for the to-be-removed item just to remove it again (deallocating the node again). This is inefficient. Second, because on detach(), we drop our own reference to the old data early on in the function, a separate thread can drop its reference and delete the old data while we still execute and reference 'key', which may be an alias to a key in the old data. This was known and we protected against it by taking _an extra copy_ of *this to retain the reference to the old data for the remainder of the function. This is also inefficient, because, while we anyway deep-copy, the ref-count manipulations, being atomic, may actually dominate the runtime for small maps. So, as described in general in the detach-remove epic (QTBUG-103329), build a new container, and swap() it into place. This solves both problems while minimizing overhead. Not picking to 6.8 and earlier, as we don't need to (optimization) and the diff is very localized, so shouldn't affect cherry-picking of fixes in adjacent lines. Task-number: QTBUG-106170 Pick-to: 6.11 6.10 Change-Id: Ia9a9e72c85398e85c963c309b42d94482a4b6759 Reviewed-by: Allan Sandfeld Jensen <allan.jensen@qt.io>
1 parent 49a6626 commit 90b68cb

File tree

1 file changed

+40
-4
lines changed

1 file changed

+40
-4
lines changed

src/corelib/tools/qmap.h

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#include <QtCore/qlist.h>
1313
#include <QtCore/qrefcount.h>
1414
#include <QtCore/qpair.h>
15+
#include <QtCore/qscopeguard.h>
1516
#include <QtCore/qshareddata.h>
1617
#include <QtCore/qshareddata_impl.h>
1718
#include <QtCore/qttypetraits.h>
@@ -328,10 +329,45 @@ class QMap
328329
if (!d)
329330
return T();
330331

331-
const auto copy = d.isShared() ? *this : QMap(); // keep `key` alive across the detach
332-
// TODO: improve. There is no need of copying all the
333-
// elements (the one to be removed can be skipped).
334-
detach();
332+
if (d.isShared()) {
333+
Map m;
334+
// For historic reasons, we always un-share (was: detach()) when
335+
// this function is called, even if `key` isn't found
336+
const auto commit = qScopeGuard([&] { QMap{std::move(m)}.swap(*this); });
337+
338+
// This way of copying ought to be O(N) (not NlogN) and not causing
339+
// any rebalancings in `m`, because we build in-order and with hint
340+
// [[citation needed]].
341+
342+
const auto keep = [&m] (auto it) { m.insert(m.cend(), *it); };
343+
344+
auto it = d->m.cbegin();
345+
const auto end = d->m.cend();
346+
const auto cmp = d->m.key_comp();
347+
while (it != end) {
348+
if (cmp(it->first, key)) { // still before
349+
keep(it);
350+
++it;
351+
} else if (cmp(key, it->first)) { // after, iow: not found
352+
// This should be faster than an actual range-insert, because
353+
// the latter cannot assume that the input is sorted; we can:
354+
while (it != end) {
355+
keep(it);
356+
++it;
357+
}
358+
break;
359+
} else { // found!
360+
return [&] {
361+
T r = it->second; // we cannot move (isShared()!)
362+
while (++it != end)
363+
keep(it);
364+
return r;
365+
}();
366+
}
367+
}
368+
// if we reach here, `key` wasn't found:
369+
return T();
370+
}
335371

336372
#ifdef __cpp_lib_node_extract
337373
if (const auto node = d->m.extract(key))

0 commit comments

Comments
 (0)