From 2af04860f6536bbbf82ee21d6aa95ca33a60fbf5 Mon Sep 17 00:00:00 2001 From: Robin Burchell Date: Fri, 3 May 2019 20:02:10 +0200 Subject: qtimezoneprivate_tz: Apply a cache over the top of timezone data Constantly re-reading the timezone information only to be told the exact same thing is wildly expensive, which can hurt in operations that cause a lot of QTimeZone creation, for example, V4's DateObject - which creates them a lot (in DaylightSavingTA). This performance problem was identified when I noticed that a QDateTime binding updated once per frame was causing >100% CPU usage (on a desktop!) thanks to a QtQuickControls 1 Calendar (which has a number of bindings to the date's properties like getMonth() and so on). The newly added tst_QTimeZone::systemTimeZone benchmark gets a ~90% decrease in instruction count: --- before +++ after PASS : tst_QTimeZone::systemTimeZone() RESULT : tst_QTimeZone::systemTimeZone(): - 0.024 msecs per iteration (total: 51, iterations: 2048) + 0.0036 msecs per iteration (total: 59, iterations: 16384) Also impacted (over in QDateTime) is tst_QDateTime::setMSecsSinceEpochTz(). The results here are - on the surface - less impressive (~0.17% drop), however, it isn't even creating QTimeZone on a hot path to begin with, so a large drop would have been a surprise. Added several further benchmarks to cover non-system zones and traverse transitions. Done-With: Edward Welbourne Task-number: QTBUG-75585 Change-Id: I044a84fc2d3a2dc965f63cd3a3299fc509750bf7 Reviewed-by: Ulf Hermann Reviewed-by: Simon Hausmann --- tests/benchmarks/corelib/time/qtimezone/main.cpp | 122 +++++++++++++++++++++++ 1 file changed, 122 insertions(+) (limited to 'tests/benchmarks/corelib') diff --git a/tests/benchmarks/corelib/time/qtimezone/main.cpp b/tests/benchmarks/corelib/time/qtimezone/main.cpp index 65455a7261..133e6451bc 100644 --- a/tests/benchmarks/corelib/time/qtimezone/main.cpp +++ b/tests/benchmarks/corelib/time/qtimezone/main.cpp @@ -1,5 +1,6 @@ /**************************************************************************** ** +** Copyright (C) 2019 Crimson AS ** Copyright (C) 2018 Klaralvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author David Faure ** Contact: https://www.qt.io/licensing/ ** @@ -30,14 +31,57 @@ #include #include +// Enable to test *every* zone, rather than a hand-picked few, in some _data() sets: +// #define EXHAUSTIVE + class tst_QTimeZone : public QObject { Q_OBJECT private Q_SLOTS: void isTimeZoneIdAvailable(); + void systemTimeZone(); + void zoneByName_data(); + void zoneByName(); + void transitionList_data(); + void transitionList(); + void transitionsForward_data() { transitionList_data(); } + void transitionsForward(); + void transitionsReverse_data() { transitionList_data(); } + void transitionsReverse(); }; +static QVector enoughZones() +{ +#ifdef EXHAUSTIVE + auto available = QTimeZone::availableTimeZoneIds(); + QVector result; + result.reserve(available.size() + 1); + for (conat auto &name : available) + result << name; +#else + QVector result{ + QByteArray("UTC"), + // Those named overtly in tst_QDateTime: + QByteArray("Europe/Oslo"), + QByteArray("America/Vancouver"), + QByteArray("Europe/Berlin"), + QByteArray("America/Sao_Paulo"), + QByteArray("Pacific/Auckland"), + QByteArray("Australia/Eucla"), + QByteArray("Asia/Kathmandu"), + QByteArray("Pacific/Kiritimati"), + QByteArray("Pacific/Apia"), + QByteArray("UTC+12:00"), + QByteArray("Australia/Sydney"), + QByteArray("Asia/Singapore"), + QByteArray("Australia/Brisbane") + }; +#endif + result << QByteArray("Vulcan/ShiKahr"); // invalid: also worth testing + return result; +} + void tst_QTimeZone::isTimeZoneIdAvailable() { const QList available = QTimeZone::availableTimeZoneIds(); @@ -47,6 +91,84 @@ void tst_QTimeZone::isTimeZoneIdAvailable() } } +void tst_QTimeZone::systemTimeZone() +{ + QBENCHMARK { + QTimeZone::systemTimeZone(); + } +} + +void tst_QTimeZone::zoneByName_data() +{ + QTest::addColumn("name"); + + const auto names = enoughZones(); + for (const auto &name : names) + QTest::newRow(name.constData()) << name; +} + +void tst_QTimeZone::zoneByName() +{ + QFETCH(QByteArray, name); + QTimeZone zone; + QBENCHMARK { + zone = QTimeZone(name); + } + Q_UNUSED(zone); +} + +void tst_QTimeZone::transitionList_data() +{ + QTest::addColumn("name"); + QTest::newRow("system") << QByteArray(); // Handled specially in the test. + + const auto names = enoughZones(); + for (const auto &name : names) { + QTimeZone zone(name); + if (zone.isValid() && zone.hasTransitions()) + QTest::newRow(name.constData()) << name; + } +} + +void tst_QTimeZone::transitionList() +{ + QFETCH(QByteArray, name); + const QTimeZone zone = name.isEmpty() ? QTimeZone::systemTimeZone() : QTimeZone(name); + const QDateTime early = QDate(1625, 6, 8).startOfDay(Qt::UTC); // Cassini's birth date + const QDateTime late // End of 32-bit signed time_t + = QDateTime::fromSecsSinceEpoch(std::numeric_limits::max(), Qt::UTC); + QTimeZone::OffsetDataList seq; + QBENCHMARK { + seq = zone.transitions(early, late); + } + Q_UNUSED(seq); +} + +void tst_QTimeZone::transitionsForward() +{ + QFETCH(QByteArray, name); + const QTimeZone zone = name.isEmpty() ? QTimeZone::systemTimeZone() : QTimeZone(name); + const QDateTime early = QDate(1625, 6, 8).startOfDay(Qt::UTC); // Cassini's birth date + QBENCHMARK { + QTimeZone::OffsetData tran = zone.nextTransition(early); + while (tran.atUtc.isValid()) + tran = zone.nextTransition(tran.atUtc); + } +} + +void tst_QTimeZone::transitionsReverse() +{ + QFETCH(QByteArray, name); + const QTimeZone zone = name.isEmpty() ? QTimeZone::systemTimeZone() : QTimeZone(name); + const QDateTime late // End of 32-bit signed time_t + = QDateTime::fromSecsSinceEpoch(std::numeric_limits::max(), Qt::UTC); + QBENCHMARK { + QTimeZone::OffsetData tran = zone.previousTransition(late); + while (tran.atUtc.isValid()) + tran = zone.previousTransition(tran.atUtc); + } +} + QTEST_MAIN(tst_QTimeZone) #include "main.moc" -- cgit v1.2.3