Refactor QVersionNumber so it stores values in-class
The common case of QVersionNumber is that there are few segments and each segment is a small integers. So instead of allocating a QVector<int>, just store those numbers in the class itself if possible. Think of this as a "Small String Optimization" for QVersionNumber. QVector<int> costs 16 + 4*N bytes, plus malloc overhead. After this change, QVersionNumber(5,4,0) will have an overhead of zero. The memory layout is explained in the header. I've coded it so big endian also works, but I have not tested it at all. Aside from the special functions for QVersionNumber and operator>>, all the rest of the algorithm could have been left unchanged. I only updated segments(), normalized(), compare(), commonPrefix() and fromString() to take advantage of the smaller implementation in a more efficient way. Note: QVersionNumber's constructors often leave half of the object or more uninitialized. That's not a problem. Change-Id: I4a2a0ce09fce2580f02d678e2f80b1dba74bac9d Reviewed-by: Marc Mutz <marc.mutz@kdab.com>
This commit is contained in:
parent
d7e23c1011
commit
2d981e7e9f
@ -169,6 +169,9 @@ QT_BEGIN_NAMESPACE
|
||||
*/
|
||||
QVector<int> QVersionNumber::segments() const
|
||||
{
|
||||
if (m_segments.isUsingPointer())
|
||||
return *m_segments.pointer_segments;
|
||||
|
||||
QVector<int> result;
|
||||
result.resize(segmentCount());
|
||||
for (int i = 0; i < segmentCount(); ++i)
|
||||
@ -205,10 +208,14 @@ QVector<int> QVersionNumber::segments() const
|
||||
*/
|
||||
QVersionNumber QVersionNumber::normalized() const
|
||||
{
|
||||
QVector<int> segs = m_segments;
|
||||
while (segs.size() && segs.last() == 0)
|
||||
segs.pop_back();
|
||||
return QVersionNumber(qMove(segs));
|
||||
int i;
|
||||
for (i = m_segments.size(); i; --i)
|
||||
if (m_segments.at(i - 1) != 0)
|
||||
break;
|
||||
|
||||
QVersionNumber result(*this);
|
||||
result.m_segments.resize(i);
|
||||
return result;
|
||||
}
|
||||
|
||||
/*!
|
||||
@ -247,10 +254,23 @@ bool QVersionNumber::isPrefixOf(const QVersionNumber &other) const Q_DECL_NOTHRO
|
||||
*/
|
||||
int QVersionNumber::compare(const QVersionNumber &v1, const QVersionNumber &v2) Q_DECL_NOTHROW
|
||||
{
|
||||
int commonlen = qMin(v1.segmentCount(), v2.segmentCount());
|
||||
for (int i = 0; i < commonlen; ++i) {
|
||||
if (v1.segmentAt(i) != v2.segmentAt(i))
|
||||
return v1.segmentAt(i) - v2.segmentAt(i);
|
||||
int commonlen;
|
||||
|
||||
if (Q_LIKELY(!v1.m_segments.isUsingPointer() && !v2.m_segments.isUsingPointer())) {
|
||||
// we can't use memcmp because it interprets the data as unsigned bytes
|
||||
const qint8 *ptr1 = v1.m_segments.inline_segments + InlineSegmentStartIdx;
|
||||
const qint8 *ptr2 = v2.m_segments.inline_segments + InlineSegmentStartIdx;
|
||||
commonlen = qMin(v1.m_segments.size(),
|
||||
v2.m_segments.size());
|
||||
for (int i = 0; i < commonlen; ++i)
|
||||
if (int x = ptr1[i] - ptr2[i])
|
||||
return x;
|
||||
} else {
|
||||
commonlen = qMin(v1.segmentCount(), v2.segmentCount());
|
||||
for (int i = 0; i < commonlen; ++i) {
|
||||
if (v1.segmentAt(i) != v2.segmentAt(i))
|
||||
return v1.segmentAt(i) - v2.segmentAt(i);
|
||||
}
|
||||
}
|
||||
|
||||
// ran out of segments in v1 and/or v2 and need to check the first trailing
|
||||
@ -294,8 +314,10 @@ QVersionNumber QVersionNumber::commonPrefix(const QVersionNumber &v1,
|
||||
if (i == 0)
|
||||
return QVersionNumber();
|
||||
|
||||
// will use a vector
|
||||
return QVersionNumber(v1.m_segments.mid(0, i));
|
||||
// try to use the one with inline segments, if there's one
|
||||
QVersionNumber result(!v1.m_segments.isUsingPointer() ? v1 : v2);
|
||||
result.m_segments.resize(i);
|
||||
return result;
|
||||
}
|
||||
|
||||
/*!
|
||||
@ -419,6 +441,19 @@ QVersionNumber QVersionNumber::fromString(const QString &string, int *suffixInde
|
||||
return QVersionNumber(qMove(seg));
|
||||
}
|
||||
|
||||
void QVersionNumber::SegmentStorage::setVector(int len, int maj, int min, int mic)
|
||||
{
|
||||
pointer_segments = new QVector<int>;
|
||||
pointer_segments->resize(len);
|
||||
pointer_segments->data()[0] = maj;
|
||||
if (len > 1) {
|
||||
pointer_segments->data()[1] = min;
|
||||
if (len > 2) {
|
||||
pointer_segments->data()[2] = mic;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef QT_NO_DATASTREAM
|
||||
/*!
|
||||
\fn QDataStream& operator<<(QDataStream &out,
|
||||
@ -445,7 +480,9 @@ QDataStream& operator<<(QDataStream &out, const QVersionNumber &version)
|
||||
*/
|
||||
QDataStream& operator>>(QDataStream &in, QVersionNumber &version)
|
||||
{
|
||||
in >> version.m_segments;
|
||||
if (!version.m_segments.isUsingPointer())
|
||||
version.m_segments.pointer_segments = new QVector<int>;
|
||||
in >> *version.m_segments.pointer_segments;
|
||||
return in;
|
||||
}
|
||||
#endif
|
||||
|
@ -53,20 +53,169 @@ Q_CORE_EXPORT QDataStream& operator>>(QDataStream &in, QVersionNumber &version);
|
||||
|
||||
class QVersionNumber
|
||||
{
|
||||
/*
|
||||
* QVersionNumber stores small values inline, without memory allocation.
|
||||
* We do that by setting the LSB in the pointer that would otherwise hold
|
||||
* the longer form of the segments.
|
||||
* The constants below help us deal with the permutations for 32- and 64-bit,
|
||||
* little- and big-endian architectures.
|
||||
*/
|
||||
enum {
|
||||
// in little-endian, inline_segments[0] is shared with the pointer's LSB, while
|
||||
// in big-endian, it's inline_segments[7]
|
||||
InlineSegmentMarker = Q_BYTE_ORDER == Q_LITTLE_ENDIAN ? 0 : sizeof(void*) - 1,
|
||||
InlineSegmentStartIdx = !InlineSegmentMarker, // 0 for BE, 1 for LE
|
||||
InlineSegmentCount = sizeof(void*) - 1
|
||||
};
|
||||
Q_STATIC_ASSERT(InlineSegmentCount >= 3); // at least major, minor, micro
|
||||
|
||||
struct SegmentStorage {
|
||||
// Note: we alias the use of dummy and inline_segments in the use of the
|
||||
// union below. This is undefined behavior in C++98, but most compilers implement
|
||||
// the C++11 behavior. The one known exception is older versions of Sun Studio.
|
||||
union {
|
||||
quintptr dummy;
|
||||
qint8 inline_segments[sizeof(void*)];
|
||||
QVector<int> *pointer_segments;
|
||||
};
|
||||
|
||||
// set the InlineSegmentMarker and set length to zero
|
||||
SegmentStorage() Q_DECL_NOTHROW : dummy(1) {}
|
||||
|
||||
SegmentStorage(const QVector<int> &seg)
|
||||
{
|
||||
if (dataFitsInline(seg.begin(), seg.size()))
|
||||
setInlineData(seg.begin(), seg.size());
|
||||
else
|
||||
pointer_segments = new QVector<int>(seg);
|
||||
}
|
||||
|
||||
SegmentStorage(const SegmentStorage &other)
|
||||
{
|
||||
if (other.isUsingPointer())
|
||||
pointer_segments = new QVector<int>(*other.pointer_segments);
|
||||
else
|
||||
dummy = other.dummy;
|
||||
}
|
||||
|
||||
SegmentStorage &operator=(const SegmentStorage &other)
|
||||
{
|
||||
if (isUsingPointer() && other.isUsingPointer()) {
|
||||
*pointer_segments = *other.pointer_segments;
|
||||
} else if (other.isUsingPointer()) {
|
||||
pointer_segments = new QVector<int>(*other.pointer_segments);
|
||||
} else {
|
||||
if (isUsingPointer())
|
||||
delete pointer_segments;
|
||||
dummy = other.dummy;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
#ifdef Q_COMPILER_RVALUE_REFS
|
||||
SegmentStorage(SegmentStorage &&other) Q_DECL_NOTHROW
|
||||
: dummy(other.dummy)
|
||||
{
|
||||
other.dummy = 1;
|
||||
}
|
||||
|
||||
SegmentStorage &operator=(SegmentStorage &&other) Q_DECL_NOTHROW
|
||||
{
|
||||
qSwap(dummy, other.dummy);
|
||||
return *this;
|
||||
}
|
||||
|
||||
explicit SegmentStorage(QVector<int> &&seg)
|
||||
{
|
||||
if (dataFitsInline(seg.begin(), seg.size()))
|
||||
setInlineData(seg.begin(), seg.size());
|
||||
else
|
||||
pointer_segments = new QVector<int>(std::move(seg));
|
||||
}
|
||||
#endif
|
||||
#ifdef Q_COMPILER_INITIALIZER_LISTS
|
||||
SegmentStorage(std::initializer_list<int> args)
|
||||
{
|
||||
if (dataFitsInline(args.begin(), int(args.size()))) {
|
||||
setInlineData(args.begin(), int(args.size()));
|
||||
} else {
|
||||
pointer_segments = new QVector<int>(args);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
~SegmentStorage() { if (isUsingPointer()) delete pointer_segments; }
|
||||
|
||||
bool isUsingPointer() const Q_DECL_NOTHROW
|
||||
{ return (inline_segments[InlineSegmentMarker] & 1) == 0; }
|
||||
|
||||
int size() const Q_DECL_NOTHROW
|
||||
{ return isUsingPointer() ? pointer_segments->size() : (inline_segments[InlineSegmentMarker] >> 1); }
|
||||
|
||||
void setInlineSize(int len)
|
||||
{ inline_segments[InlineSegmentMarker] = 1 + 2 * len; }
|
||||
|
||||
void resize(int len)
|
||||
{
|
||||
if (isUsingPointer())
|
||||
pointer_segments->resize(len);
|
||||
else
|
||||
setInlineSize(len);
|
||||
}
|
||||
|
||||
int at(int index) const
|
||||
{
|
||||
return isUsingPointer() ?
|
||||
pointer_segments->at(index) :
|
||||
inline_segments[InlineSegmentStartIdx + index];
|
||||
}
|
||||
|
||||
void setSegments(int len, int maj, int min = 0, int mic = 0)
|
||||
{
|
||||
if (maj == qint8(maj) && min == qint8(min) && mic == qint8(mic)) {
|
||||
int data[] = { maj, min, mic };
|
||||
setInlineData(data, len);
|
||||
} else {
|
||||
setVector(len, maj, min, mic);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
static bool dataFitsInline(const int *data, int len)
|
||||
{
|
||||
if (len > InlineSegmentCount)
|
||||
return false;
|
||||
for (int i = 0; i < len; ++i)
|
||||
if (data[i] != qint8(data[i]))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
void setInlineData(const int *data, int len)
|
||||
{
|
||||
setInlineSize(len);
|
||||
for (int i = 0; i < len; ++i)
|
||||
inline_segments[InlineSegmentStartIdx + i] = qint8(data[i]);
|
||||
}
|
||||
|
||||
Q_CORE_EXPORT void setVector(int len, int maj, int min, int mic);
|
||||
} m_segments;
|
||||
|
||||
public:
|
||||
inline QVersionNumber() Q_DECL_NOTHROW
|
||||
: m_segments()
|
||||
{}
|
||||
// compiler-generated copy/move ctor/assignment operators are ok
|
||||
|
||||
inline explicit QVersionNumber(const QVector<int> &seg) Q_DECL_NOTHROW
|
||||
inline explicit QVersionNumber(const QVector<int> &seg)
|
||||
: m_segments(seg)
|
||||
{}
|
||||
|
||||
// compiler-generated copy/move ctor/assignment operators and the destructor are ok
|
||||
|
||||
#ifdef Q_COMPILER_RVALUE_REFS
|
||||
inline explicit QVersionNumber(QVector<int> &&seg) Q_DECL_NOTHROW
|
||||
: m_segments(qMove(seg))
|
||||
explicit QVersionNumber(QVector<int> &&seg)
|
||||
: m_segments(std::move(seg))
|
||||
{}
|
||||
#endif
|
||||
|
||||
#ifdef Q_COMPILER_INITIALIZER_LISTS
|
||||
inline QVersionNumber(std::initializer_list<int> args)
|
||||
: m_segments(args)
|
||||
@ -74,13 +223,13 @@ public:
|
||||
#endif
|
||||
|
||||
inline explicit QVersionNumber(int maj)
|
||||
{ m_segments.reserve(1); m_segments << maj; }
|
||||
{ m_segments.setSegments(1, maj); }
|
||||
|
||||
inline explicit QVersionNumber(int maj, int min)
|
||||
{ m_segments.reserve(2); m_segments << maj << min; }
|
||||
{ m_segments.setSegments(2, maj, min); }
|
||||
|
||||
inline explicit QVersionNumber(int maj, int min, int mic)
|
||||
{ m_segments.reserve(3); m_segments << maj << min << mic; }
|
||||
{ m_segments.setSegments(3, maj, min, mic); }
|
||||
|
||||
inline bool isNull() const Q_DECL_NOTHROW Q_REQUIRED_RESULT
|
||||
{ return segmentCount() == 0; }
|
||||
@ -121,8 +270,6 @@ private:
|
||||
friend Q_CORE_EXPORT QDataStream& operator>>(QDataStream &in, QVersionNumber &version);
|
||||
#endif
|
||||
friend Q_CORE_EXPORT uint qHash(const QVersionNumber &key, uint seed);
|
||||
|
||||
QVector<int> m_segments;
|
||||
};
|
||||
|
||||
Q_DECLARE_TYPEINFO(QVersionNumber, Q_MOVABLE_TYPE);
|
||||
|
Loading…
x
Reference in New Issue
Block a user