Long live the short QDateTime optimization

Well, maybe not for so long: we may want to revisit it for Qt 6. At the
very least, we should enlarge the size of QDateTime on 32-bit system so
that they too can benefit from the optimization.

With this optimization, on 64-bit systems, the most common uses of
QDateTime now no longer allocate memory at all. The range is slightly
reduced from 584,554,049 years to 2,283,414 years around 1970. The other
drawback is that calling QDateTime::offsetFromUtc() on a localtime now
needs to recalculate the offset, instead of using the cached offset.
(QDateTime::toMSecsSinceEpoch() didn't use the cache).

Change-Id: Id5480807d25e49e78b79ffff144a8b2c9af91814
Reviewed-by: Edward Welbourne <edward.welbourne@qt.io>
This commit is contained in:
Thiago Macieira 2016-05-01 13:20:52 -07:00
parent 3eb5b15d80
commit fb498a6519
3 changed files with 280 additions and 75 deletions

View File

@ -68,8 +68,6 @@
QT_BEGIN_NAMESPACE
Q_GLOBAL_STATIC_WITH_ARGS(QSharedDataPointer<QDateTimePrivate>, defaultDateTimePrivate, (new QDateTimePrivate()))
/*****************************************************************************
Date/Time Constants
*****************************************************************************/
@ -2121,6 +2119,10 @@ int QTime::elapsed() const
QDateTime static helper functions
*****************************************************************************/
// get the types from QDateTime (through QDateTimePrivate)
typedef QDateTimePrivate::QDateTimeShortData ShortData;
typedef QDateTimePrivate::QDateTimeData QDateTimeData;
// Calls the platform variant of tzset
static void qt_tzset()
{
@ -2516,55 +2518,151 @@ QDateTimePrivate::DaylightStatus extractDaylightStatus(QDateTimePrivate::StatusF
return QDateTimePrivate::UnknownDaylightTime;
}
static inline qint64 getMSecs(const QDateTimePrivate *d)
static inline qint64 getMSecs(const QDateTimeData &d)
{
if (d.isShort()) {
// same as, but producing better code
//return d.data.msecs;
return qintptr(d.d) >> 8;
}
return d->m_msecs;
}
static inline QDateTimePrivate::StatusFlags getStatus(const QDateTimePrivate *d)
static inline QDateTimePrivate::StatusFlags getStatus(const QDateTimeData &d)
{
if (d.isShort()) {
// same as, but producing better code
//return StatusFlag(d.data.status);
return QDateTimePrivate::StatusFlag(qintptr(d.d) & 0xFF);
}
return d->m_status;
}
static inline Qt::TimeSpec getSpec(const QDateTimePrivate *d)
static inline Qt::TimeSpec getSpec(const QDateTimeData &d)
{
return extractSpec(getStatus(d));
}
/*****************************************************************************
QDateTime::Data member functions
*****************************************************************************/
inline QDateTime::Data::Data(Qt::TimeSpec spec)
{
if (CanBeSmall && Q_LIKELY(spec == Qt::LocalTime || spec == Qt::UTC)) {
d = reinterpret_cast<QDateTimePrivate *>(int(mergeSpec(QDateTimePrivate::ShortData, spec)));
} else {
// the structure is too small, we need to detach
d = new QDateTimePrivate;
d->ref.ref();
d->m_status = mergeSpec(0, spec);
}
}
inline QDateTime::Data::Data(const Data &other)
: d(other.d)
{
if (!isShort())
d->ref.ref();
}
inline QDateTime::Data &QDateTime::Data::operator=(const Data &other)
{
if (d == other.d)
return *this;
auto x = d;
d = other.d;
if (!other.isShort())
other.d->ref.ref();
if (!(CanBeSmall && quintptr(x) & QDateTimePrivate::ShortData) && !x->ref.deref())
delete x;
return *this;
}
inline QDateTime::Data::~Data()
{
if (!isShort() && !d->ref.deref())
delete d;
}
inline bool QDateTime::Data::isShort() const
{
return CanBeSmall && quintptr(d) & QDateTimePrivate::ShortData;
}
inline void QDateTime::Data::detach()
{
QDateTimePrivate *x;
bool wasShort = isShort();
if (wasShort) {
// force enlarging
x = new QDateTimePrivate;
x->m_status = QDateTimePrivate::StatusFlag(data.status & ~QDateTimePrivate::ShortData);
x->m_msecs = data.msecs;
} else {
if (d->ref.load() == 1)
return;
x = new QDateTimePrivate(*d);
}
x->ref.store(1);
if (!wasShort && !d->ref.deref())
delete d;
d = x;
}
inline const QDateTimePrivate *QDateTime::Data::operator->() const
{
Q_ASSERT(!isShort());
return d;
}
inline QDateTimePrivate *QDateTime::Data::operator->()
{
// should we attempt to detach here?
Q_ASSERT(!isShort());
Q_ASSERT(d->ref.load() == 1);
return d;
}
/*****************************************************************************
QDateTimePrivate member functions
*****************************************************************************/
static void setTimeSpec(QDateTimePrivate *d, Qt::TimeSpec spec, int offsetSeconds);
static void setDateTime(QDateTimePrivate *d, const QDate &date, const QTime &time);
static QPair<QDate, QTime> getDateTime(const QDateTimePrivate *d);
static void checkValidDateTime(QDateTimePrivate *d);
static void refreshDateTime(QDateTimePrivate *d);
static void setTimeSpec(QDateTimeData &d, Qt::TimeSpec spec, int offsetSeconds);
static void setDateTime(QDateTimeData &d, const QDate &date, const QTime &time);
static QPair<QDate, QTime> getDateTime(const QDateTimeData &d);
static void checkValidDateTime(QDateTimeData &d);
static void refreshDateTime(QDateTimeData &d);
QDateTimePrivate::QDateTimePrivate(const QDate &toDate, const QTime &toTime, Qt::TimeSpec toSpec,
int offsetSeconds)
: m_msecs(0),
m_status(0),
m_offsetFromUtc(0),
ref(0)
Q_NEVER_INLINE
QDateTime::Data QDateTimePrivate::create(const QDate &toDate, const QTime &toTime, Qt::TimeSpec toSpec,
int offsetSeconds)
{
setTimeSpec(this, toSpec, offsetSeconds);
setDateTime(this, toDate, toTime);
QDateTime::Data result(toSpec);
setTimeSpec(result, toSpec, offsetSeconds);
setDateTime(result, toDate, toTime);
return result;
}
#ifndef QT_BOOTSTRAPPED
QDateTimePrivate::QDateTimePrivate(const QDate &toDate, const QTime &toTime,
const QTimeZone &toTimeZone)
: m_status(mergeSpec(0, Qt::TimeZone)),
m_offsetFromUtc(0),
ref(0),
m_timeZone(toTimeZone)
inline QDateTime::Data QDateTimePrivate::create(const QDate &toDate, const QTime &toTime,
const QTimeZone &toTimeZone)
{
setDateTime(this, toDate, toTime);
QDateTime::Data result(Qt::TimeZone);
Q_ASSERT(!result.isShort());
result.d->m_status = mergeSpec(result.d->m_status, Qt::TimeZone);
result.d->m_timeZone = toTimeZone;
setDateTime(result, toDate, toTime);
return result;
}
#endif // QT_BOOTSTRAPPED
static void setTimeSpec(QDateTimePrivate *d, Qt::TimeSpec spec, int offsetSeconds)
static void setTimeSpec(QDateTimeData &d, Qt::TimeSpec spec, int offsetSeconds)
{
auto status = getStatus(d);
status &= ~(QDateTimePrivate::ValidDateTime | QDateTimePrivate::DaylightMask |
@ -2586,14 +2684,19 @@ static void setTimeSpec(QDateTimePrivate *d, Qt::TimeSpec spec, int offsetSecond
}
status = mergeSpec(status, spec);
d->m_status = status;
d->m_offsetFromUtc = offsetSeconds;
if (d.isShort() && offsetSeconds == 0) {
d.data.status = status;
} else {
d.detach();
d->m_status = status & ~QDateTimePrivate::ShortData;
d->m_offsetFromUtc = offsetSeconds;
#ifndef QT_BOOTSTRAPPED
d->m_timeZone = QTimeZone();
d->m_timeZone = QTimeZone();
#endif // QT_BOOTSTRAPPED
}
}
static void setDateTime(QDateTimePrivate *d, const QDate &date, const QTime &time)
static void setDateTime(QDateTimeData &d, const QDate &date, const QTime &time)
{
// If the date is valid and the time is not we set time to 00:00:00
QTime useTime = time;
@ -2617,15 +2720,31 @@ static void setDateTime(QDateTimePrivate *d, const QDate &date, const QTime &tim
}
// Set msecs serial value
d->m_msecs = (days * MSECS_PER_DAY) + ds;
d->m_status &= ~(QDateTimePrivate::ValidityMask | QDateTimePrivate::DaylightMask);
d->m_status |= newStatus;
qint64 msecs = (days * MSECS_PER_DAY) + ds;
if (d.isShort()) {
// let's see if we can keep this short
d.data.msecs = qintptr(msecs);
if (d.data.msecs == msecs) {
// yes, we can
d.data.status &= ~(QDateTimePrivate::ValidityMask | QDateTimePrivate::DaylightMask);
d.data.status |= newStatus;
} else {
// nope...
d.detach();
}
}
if (!d.isShort()) {
d.detach();
d->m_msecs = msecs;
d->m_status &= ~(QDateTimePrivate::ValidityMask | QDateTimePrivate::DaylightMask);
d->m_status |= newStatus;
}
// Set if date and time are valid
checkValidDateTime(d);
}
static QPair<QDate, QTime> getDateTime(const QDateTimePrivate *d)
static QPair<QDate, QTime> getDateTime(const QDateTimeData &d)
{
QPair<QDate, QTime> result;
qint64 msecs = getMSecs(d);
@ -2642,7 +2761,7 @@ static QPair<QDate, QTime> getDateTime(const QDateTimePrivate *d)
}
// Check the UTC / offsetFromUTC validity
static void checkValidDateTime(QDateTimePrivate *d)
static void checkValidDateTime(QDateTimeData &d)
{
auto status = getStatus(d);
auto spec = extractSpec(status);
@ -2654,7 +2773,10 @@ static void checkValidDateTime(QDateTimePrivate *d)
status |= QDateTimePrivate::ValidDateTime;
else
status &= ~QDateTimePrivate::ValidDateTime;
d->m_status = status;
if (status & QDateTimePrivate::ShortData)
d.data.status = status;
else
d->m_status = status;
break;
case Qt::TimeZone:
case Qt::LocalTime:
@ -2666,7 +2788,7 @@ static void checkValidDateTime(QDateTimePrivate *d)
}
// Refresh the LocalTime validity and offset
static void refreshDateTime(QDateTimePrivate *d)
static void refreshDateTime(QDateTimeData &d)
{
auto status = getStatus(d);
const auto spec = extractSpec(status);
@ -2690,8 +2812,12 @@ static void refreshDateTime(QDateTimePrivate *d)
// If not valid date and time then is invalid
if (!(status & QDateTimePrivate::ValidDate) || !(status & QDateTimePrivate::ValidTime)) {
status &= ~QDateTimePrivate::ValidDateTime;
d->m_status = status;
d->m_offsetFromUtc = 0;
if (status & QDateTimePrivate::ShortData) {
d.data.status = status;
} else {
d->m_status = status;
d->m_offsetFromUtc = 0;
}
return;
}
@ -2710,14 +2836,18 @@ static void refreshDateTime(QDateTimePrivate *d)
status &= ~QDateTimePrivate::ValidDateTime;
}
d->m_status = status;
d->m_offsetFromUtc = offsetFromUtc;
if (status & QDateTimePrivate::ShortData) {
d.data.status = status;
} else {
d->m_status = status;
d->m_offsetFromUtc = offsetFromUtc;
}
}
#ifndef QT_BOOTSTRAPPED
// Convert a TimeZone time expressed in zone msecs encoding into a UTC epoch msecs
qint64 QDateTimePrivate::zoneMSecsToEpochMSecs(qint64 zoneMSecs, const QTimeZone &zone,
QDate *localDate, QTime *localTime)
inline qint64 QDateTimePrivate::zoneMSecsToEpochMSecs(qint64 zoneMSecs, const QTimeZone &zone,
QDate *localDate, QTime *localTime)
{
// Get the effective data from QTimeZone
QTimeZonePrivate::Data data = zone.d->dataForLocalTime(zoneMSecs);
@ -2868,8 +2998,8 @@ qint64 QDateTimePrivate::zoneMSecsToEpochMSecs(qint64 zoneMSecs, const QTimeZone
\sa isValid()
*/
QDateTime::QDateTime()
: d(*defaultDateTimePrivate())
QDateTime::QDateTime() Q_DECL_NOEXCEPT_EXPR(Data::CanBeSmall)
: d(Qt::LocalTime)
{
}
@ -2880,7 +3010,7 @@ QDateTime::QDateTime()
*/
QDateTime::QDateTime(const QDate &date)
: d(new QDateTimePrivate(date, QTime(0, 0, 0), Qt::LocalTime, 0))
: d(QDateTimePrivate::create(date, QTime(0, 0, 0), Qt::LocalTime, 0))
{
}
@ -2900,7 +3030,7 @@ QDateTime::QDateTime(const QDate &date)
*/
QDateTime::QDateTime(const QDate &date, const QTime &time, Qt::TimeSpec spec)
: d(new QDateTimePrivate(date, time, spec, 0))
: d(QDateTimePrivate::create(date, time, spec, 0))
{
}
@ -2923,7 +3053,7 @@ QDateTime::QDateTime(const QDate &date, const QTime &time, Qt::TimeSpec spec)
*/
QDateTime::QDateTime(const QDate &date, const QTime &time, Qt::TimeSpec spec, int offsetSeconds)
: d(new QDateTimePrivate(date, time, spec, offsetSeconds))
: d(QDateTimePrivate::create(date, time, spec, offsetSeconds))
{
}
@ -2940,7 +3070,7 @@ QDateTime::QDateTime(const QDate &date, const QTime &time, Qt::TimeSpec spec, in
*/
QDateTime::QDateTime(const QDate &date, const QTime &time, const QTimeZone &timeZone)
: d(new QDateTimePrivate(date, time, timeZone))
: d(QDateTimePrivate::create(date, time, timeZone))
{
}
#endif // QT_BOOTSTRAPPED
@ -2949,7 +3079,7 @@ QDateTime::QDateTime(const QDate &date, const QTime &time, const QTimeZone &time
Constructs a copy of the \a other datetime.
*/
QDateTime::QDateTime(const QDateTime &other)
QDateTime::QDateTime(const QDateTime &other) Q_DECL_NOTHROW
: d(other.d)
{
}
@ -2966,7 +3096,7 @@ QDateTime::~QDateTime()
copy.
*/
QDateTime &QDateTime::operator=(const QDateTime &other)
QDateTime &QDateTime::operator=(const QDateTime &other) Q_DECL_NOTHROW
{
d = other.d;
return *this;
@ -3104,7 +3234,20 @@ QTimeZone QDateTime::timeZone() const
int QDateTime::offsetFromUtc() const
{
return d->m_offsetFromUtc;
if (!d.isShort())
return d->m_offsetFromUtc;
if (!isValid())
return 0;
auto spec = getSpec(d);
if (spec == Qt::LocalTime) {
// we didn't cache the value, so we need to calculate it now...
qint64 msecs = getMSecs(d);
return (msecs - toMSecsSinceEpoch()) / 1000;
}
Q_ASSERT(spec == Qt::UTC);
return 0;
}
/*!
@ -3232,7 +3375,6 @@ void QDateTime::setTime(const QTime &time)
void QDateTime::setTimeSpec(Qt::TimeSpec spec)
{
QDateTimePrivate *d = this->d.data(); // detaches (and shadows d)
QT_PREPEND_NAMESPACE(setTimeSpec(d, spec, 0));
checkValidDateTime(d);
}
@ -3272,7 +3414,7 @@ void QDateTime::setOffsetFromUtc(int offsetSeconds)
void QDateTime::setTimeZone(const QTimeZone &toZone)
{
QDateTimePrivate *d = this->d.data(); // detaches (and shadows d)
d.detach(); // always detach
d->m_status = mergeSpec(d->m_status, Qt::TimeZone);
d->m_offsetFromUtc = 0;
d->m_timeZone = toZone;
@ -3299,8 +3441,10 @@ qint64 QDateTime::toMSecsSinceEpoch() const
{
switch (getSpec(d)) {
case Qt::UTC:
return getMSecs(d);
case Qt::OffsetFromUTC:
return getMSecs(d) - (d->m_offsetFromUtc * 1000);
return d->m_msecs - (d->m_offsetFromUtc * 1000);
case Qt::LocalTime: {
// recalculate the local timezone
@ -3367,7 +3511,6 @@ uint QDateTime::toTime_t() const
*/
void QDateTime::setMSecsSinceEpoch(qint64 msecs)
{
QDateTimePrivate *d = this->d.data(); // detaches (and shadows d)
const auto spec = getSpec(d);
auto status = getStatus(d);
@ -3387,9 +3530,11 @@ void QDateTime::setMSecsSinceEpoch(qint64 msecs)
| QDateTimePrivate::ValidDateTime;
break;
case Qt::TimeZone:
Q_ASSERT(!d.isShort());
#ifndef QT_BOOTSTRAPPED
// Docs state any LocalTime before 1970-01-01 will *not* have any DST applied
// but all affected times afterwards will have DST applied.
d.detach();
if (msecs >= 0)
d->m_offsetFromUtc = d->m_timeZone.d->offsetFromUtc(msecs);
else
@ -3413,8 +3558,17 @@ void QDateTime::setMSecsSinceEpoch(qint64 msecs)
}
}
d->m_status = status;
d->m_msecs = msecs;
ShortData sd;
sd.msecs = msecs;
if (d.isShort() && sd.msecs == msecs) {
// we can keep short
sd.status = status;
d.data = sd;
} else {
d.detach();
d->m_status = status;
d->m_msecs = msecs;
}
if (spec == Qt::LocalTime || spec == Qt::TimeZone)
refreshDateTime(d);
@ -3504,7 +3658,7 @@ QString QDateTime::toString(Qt::DateFormat format) const
return QLocale().toString(*this, QLocale::LongFormat);
case Qt::RFC2822Date: {
buf = QLocale::c().toString(*this, QStringLiteral("dd MMM yyyy hh:mm:ss "));
buf += toOffsetString(Qt::TextDate, d->m_offsetFromUtc);
buf += toOffsetString(Qt::TextDate, offsetFromUtc());
return buf;
}
default:
@ -3541,7 +3695,7 @@ QString QDateTime::toString(Qt::DateFormat format) const
buf += QLatin1Char('Z');
break;
case Qt::OffsetFromUTC:
buf += toOffsetString(Qt::ISODate, d->m_offsetFromUtc);
buf += toOffsetString(Qt::ISODate, offsetFromUtc());
break;
default:
break;
@ -3630,7 +3784,7 @@ QString QDateTime::toString(const QString& format) const
}
#endif //QT_NO_DATESTRING
static inline void massageAdjustedDateTime(const QDateTimePrivate *d, QDate *date, QTime *time)
static inline void massageAdjustedDateTime(const QDateTimeData &d, QDate *date, QTime *time)
{
/*
If we have just adjusted to a day with a DST transition, our given time
@ -3764,12 +3918,24 @@ QDateTime QDateTime::addMSecs(qint64 msecs) const
QDateTime dt(*this);
auto spec = getSpec(d);
if (spec == Qt::LocalTime || spec == Qt::TimeZone)
if (spec == Qt::LocalTime || spec == Qt::TimeZone) {
// Convert to real UTC first in case crosses DST transition
dt.setMSecsSinceEpoch(toMSecsSinceEpoch() + msecs);
else
} else {
// No need to convert, just add on
dt.d->m_msecs += msecs;
if (d.isShort()) {
// need to check if we need to enlarge first
msecs += dt.d.data.msecs;
dt.d.data.msecs = qintptr(msecs);
if (dt.d.data.msecs != msecs) {
dt.d.detach();
dt.d->m_msecs = msecs;
}
} else {
dt.d.detach();
dt.d->m_msecs += msecs;
}
}
return dt;
}

View File

@ -1,6 +1,7 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Copyright (C) 2016 Intel Corporation.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtCore module of the Qt Toolkit.
@ -208,8 +209,45 @@ class QDateTimePrivate;
class Q_CORE_EXPORT QDateTime
{
// ### Qt 6: revisit the optimization
struct ShortData {
#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN
quintptr status : 8;
#endif
// note: this is only 24 bits on 32-bit systems...
qintptr msecs : sizeof(void *) * 8 - 8;
#if Q_BYTE_ORDER == Q_BIG_ENDIAN
quintptr status : 8;
#endif
};
union Data {
enum {
// To be of any use, we need at least 60 years around 1970, which
// is 1,893,456,000,000 ms. That requires 41 bits to store, plus
// the sign bit. With the status byte, the minimum size is 50 bits.
CanBeSmall = sizeof(ShortData) * 8 > 50
};
Data(Qt::TimeSpec);
Data(const Data &other);
Data &operator=(const Data &other);
// no move semantics (would be the same as copy)
~Data();
bool isShort() const;
void detach();
const QDateTimePrivate *operator->() const;
QDateTimePrivate *operator->();
QDateTimePrivate *d;
ShortData data;
};
public:
QDateTime();
QDateTime() Q_DECL_NOEXCEPT_EXPR(Data::CanBeSmall);
explicit QDateTime(const QDate &);
QDateTime(const QDate &, const QTime &, Qt::TimeSpec spec = Qt::LocalTime);
// ### Qt 6: Merge with above with default offsetSeconds = 0
@ -217,15 +255,15 @@ public:
#ifndef QT_BOOTSTRAPPED
QDateTime(const QDate &date, const QTime &time, const QTimeZone &timeZone);
#endif // QT_BOOTSTRAPPED
QDateTime(const QDateTime &other);
QDateTime(const QDateTime &other) Q_DECL_NOTHROW;
~QDateTime();
#ifdef Q_COMPILER_RVALUE_REFS
QDateTime &operator=(QDateTime &&other) Q_DECL_NOTHROW { swap(other); return *this; }
#endif
QDateTime &operator=(const QDateTime &other);
QDateTime &operator=(const QDateTime &other) Q_DECL_NOTHROW;
void swap(QDateTime &other) Q_DECL_NOTHROW { qSwap(d, other.d); }
void swap(QDateTime &other) Q_DECL_NOTHROW { qSwap(d.d, other.d.d); }
bool isNull() const;
bool isValid() const;
@ -321,10 +359,7 @@ public:
private:
friend class QDateTimePrivate;
// ### Qt6: Using a private here has high impact on runtime
// on users such as QFileInfo. In Qt 6, the data members
// should be inlined.
QSharedDataPointer<QDateTimePrivate> d;
Data d;
#ifndef QT_NO_DATASTREAM
friend Q_CORE_EXPORT QDataStream &operator<<(QDataStream &, const QDateTime &);

View File

@ -64,6 +64,10 @@ QT_BEGIN_NAMESPACE
class QDateTimePrivate
{
public:
// forward the declarations from QDateTime (this makes them public)
typedef QDateTime::ShortData QDateTimeShortData;
typedef QDateTime::Data QDateTimeData;
// Never change or delete this enum, it is required for backwards compatible
// serialization of QDateTime before 5.2, so is essentially public API
enum Spec {
@ -110,11 +114,11 @@ public:
{
}
QDateTimePrivate(const QDate &toDate, const QTime &toTime, Qt::TimeSpec toSpec,
int offsetSeconds);
static QDateTime::Data create(const QDate &toDate, const QTime &toTime, Qt::TimeSpec toSpec,
int offsetSeconds);
#ifndef QT_BOOTSTRAPPED
QDateTimePrivate(const QDate &toDate, const QTime &toTime, const QTimeZone & timeZone);
static QDateTime::Data create(const QDate &toDate, const QTime &toTime, const QTimeZone & timeZone);
#endif // QT_BOOTSTRAPPED
qint64 m_msecs;