Add QList::emplaceFront() that fixes huge performance issue in prepend

Prepend in QList was using insert() logic that always uses append-aware
functions. This results in the fact that freeSpaceAtBegin() is always 0
and forces us to actually allocate on *every* call (with the same
capacity!). Vicious cycle is hot-fixable with introduction of
emplaceFront (or anything prepend-aware, really)

This brings me from 632ms to 0.65ms for 100k iterations of
list.prepend(int(0)). Still ~3x worse than QList in 5.15 but much
faster than QVector, which takes 382ms in the same workload

Not addressed:
- QString/QBA
- Other prepend functions in QList e.g. prepend(it1, it2)
- Lower-level array operations that should just be extended

Task-number: QTBUG-86583
Change-Id: Ie82b07d81a67605cd308d9fabf9532d57935647f
Reviewed-by: Lars Knoll <lars.knoll@qt.io>
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
This commit is contained in:
Andrei Golubev 2020-10-29 12:11:49 +01:00 committed by Lars Knoll
parent 02a5928fa5
commit 12718e5ea2

View File

@ -316,12 +316,15 @@ public:
void append(rvalue_ref t) { emplaceBack(std::move(t)); }
void append(const QList<T> &l) { append(l.constBegin(), l.constEnd()); }
void append(QList<T> &&l);
void prepend(rvalue_ref t);
void prepend(parameter_type t);
void prepend(rvalue_ref t) { emplaceFront(std::move(t)); }
void prepend(parameter_type t) { emplaceFront(t); }
template <typename ...Args>
reference emplaceBack(Args&&... args) { return *emplace(count(), std::forward<Args>(args)...); }
template <typename ...Args>
inline reference emplaceFront(Args&&... args);
iterator insert(qsizetype i, parameter_type t)
{ return insert(i, 1, t); }
iterator insert(qsizetype i, qsizetype n, parameter_type t);
@ -651,13 +654,6 @@ inline void QList<T>::remove(qsizetype i, qsizetype n)
}
}
template <typename T>
inline void QList<T>::prepend(parameter_type t)
{ insert(0, 1, t); }
template <typename T>
void QList<T>::prepend(rvalue_ref t)
{ insert(0, std::move(t)); }
template<typename T>
inline T QList<T>::value(qsizetype i, parameter_type defaultValue) const
{
@ -711,6 +707,29 @@ inline void QList<T>::append(QList<T> &&other)
}
}
template<typename T>
template<typename... Args>
inline typename QList<T>::reference QList<T>::emplaceFront(Args &&... args)
{
const bool shouldGrow = d->shouldGrowBeforeInsert(d.begin(), 1);
const auto newSize = size() + 1;
if (d->needsDetach() || newSize > d->constAllocatedCapacity() || shouldGrow) {
const auto flags = d->detachFlags() | Data::GrowsBackwards;
DataPointer detached(DataPointer::allocateGrow(d, newSize, flags));
T tmp(std::forward<Args>(args)...);
detached->copyAppend(constBegin(), constEnd());
// insert here makes sure we have extra free space at beginning. we
// actually need a proper copyPrepend here instead.
detached->insert(detached.begin(), 1, std::move(tmp));
d.swap(detached);
} else {
// ### replace with emplaceFront
d->emplace(d.begin(), std::forward<Args>(args)...);
}
return *d.begin();
}
template <typename T>
inline typename QList<T>::iterator