From 3070efb64a628f5c61fc5870ee4a8791942f6699 Mon Sep 17 00:00:00 2001 From: Marc Mutz Date: Tue, 11 Feb 2025 16:34:13 +0100 Subject: [PATCH] QCbor/JsonValue: add toStringView() This is like toString(), but returns a view on the underlying string data (which may be US-ASCII (= L1), UTF-8, or UTF-16, so the return value is QASV). Use it around the code a bit. Benchmark results on my machine (with -perf -iterations 1000): PASS : BenchmarkQtJson::iteratorKey() RESULT : BenchmarkQtJson::iteratorKey(): 78,268.426 nsecs per iteration (total: 78,268,427, iterations: 1000) 349,458.080 CPU cycles per iteration, 4,46 GHz (total: 349,458,080, iterations: 1000) 780,327.957 instructions per iteration, 2,233 instr/cycle (total: 780,327,957, iterations: 1000) 173,511.019 branch instructions per iteration, 2,22 G/sec (total: 173,511,020, iterations: 1000) PASS : BenchmarkQtJson::iteratorKeyValueView() RESULT : BenchmarkQtJson::iteratorKeyValueView(): 58,424.756 nsecs per iteration (total: 58,424,756, iterations: 1000) 267,176.603 CPU cycles per iteration, 4,57 GHz (total: 267,176,604, iterations: 1000) 614,805.388 instructions per iteration, 2,301 instr/cycle (total: 614,805,389, iterations: 1000) 140,681.105 branch instructions per iteration, 2,41 G/sec (total: 140,681,106, iterations: 1000) PASS : BenchmarkQtJson::iteratorKeyView() RESULT : BenchmarkQtJson::iteratorKeyView(): 44,060.925 nsecs per iteration (total: 44,060,925, iterations: 1000) 196,344.233 CPU cycles per iteration, 4,46 GHz (total: 196,344,233, iterations: 1000) 426,631.322 instructions per iteration, 2,173 instr/cycle (total: 426,631,323, iterations: 1000) 93,703.100 branch instructions per iteration, 2,13 G/sec (total: 93,703,101, iterations: 1000) PASS : BenchmarkQtJson::iteratorKeyViewValueView() RESULT : BenchmarkQtJson::iteratorKeyViewValueView(): 29,205.569 nsecs per iteration (total: 29,205,570, iterations: 1000) 131,089.040 CPU cycles per iteration, 4,49 GHz (total: 131,089,041, iterations: 1000) 258,529.469 instructions per iteration, 1,972 instr/cycle (total: 258,529,469, iterations: 1000) 60,875.631 branch instructions per iteration, 2,08 G/sec (total: 60,875,631, iterations: 1000) [ChangeLog][QtCore][QCborValue/QJsonValue] Add toStringView() method. Fixes: QTBUG-133688 Change-Id: I4200035e995426f1cce46a73a429921a0cd56c85 Reviewed-by: Thiago Macieira --- src/corelib/serialization/qcborvalue.cpp | 37 ++++++++++++++++++- src/corelib/serialization/qcborvalue.h | 5 +++ src/corelib/serialization/qjsonvalue.cpp | 31 ++++++++++++++++ src/corelib/serialization/qjsonvalue.h | 6 +++ src/gui/opengl/qopengl.cpp | 4 +- .../kmsconvenience/qkmsdevice.cpp | 5 ++- .../qcborvalue/tst_qcborvalue.cpp | 17 +++++++-- .../corelib/json/tst_bench_qtjson.cpp | 11 ++++++ 8 files changed, 108 insertions(+), 8 deletions(-) diff --git a/src/corelib/serialization/qcborvalue.cpp b/src/corelib/serialization/qcborvalue.cpp index e9b7d5b71b6..2e61ed7460d 100644 --- a/src/corelib/serialization/qcborvalue.cpp +++ b/src/corelib/serialization/qcborvalue.cpp @@ -2195,7 +2195,7 @@ QByteArray QCborValue::toByteArray(const QByteArray &defaultValue) const Note that this function performs no conversion from other types to QString. - \sa isString(), isByteArray(), toByteArray() + \sa toStringView(), isString(), isByteArray(), toByteArray() */ QString QCborValue::toString(const QString &defaultValue) const { @@ -2206,6 +2206,33 @@ QString QCborValue::toString(const QString &defaultValue) const return container->stringAt(n); } +/*! + \since 6.10 + + Returns the string value stored in this QCborValue, if it is of the string + type. Otherwise, it returns \a defaultValue. Since QCborValue stores + strings in either US-ASCII, UTF-8 or UTF-16, the returned QAnyStringView + may be in any of these encodings. + + This function does not allocate memory. The return value is valid until the + next call to a non-const member function on this object. If this object goes + out of scope, the return value is valid until the next call to a non-const + member function on the parent CBOR object (map or array). + + Note that this function performs no conversion from other types to + QString. + + \sa toString(), isString(), isByteArray(), toByteArray() +*/ +QAnyStringView QCborValue::toStringView(QAnyStringView defaultValue) const +{ + if (!container || !isString()) + return defaultValue; + + Q_ASSERT(n >= 0); + return container->anyStringViewAt(n); +} + #if QT_CONFIG(datestring) /*! Returns the date/time value stored in this QCborValue, if it is of the @@ -2901,6 +2928,14 @@ QString QCborValueConstRef::concreteString(QCborValueConstRef self, const QStrin return self.d->stringAt(self.i); } +QAnyStringView QCborValueConstRef::concreteStringView(QCborValueConstRef self, QAnyStringView defaultValue) +{ + QtCbor::Element e = self.d->elements.at(self.i); + if (e.type != QCborValue::String) + return defaultValue; + return self.d->anyStringViewAt(self.i); +} + bool QCborValueConstRef::comparesEqual_helper(QCborValueConstRef lhs, QCborValueConstRef rhs) noexcept { diff --git a/src/corelib/serialization/qcborvalue.h b/src/corelib/serialization/qcborvalue.h index 4f4349bee33..3b20ac4487b 100644 --- a/src/corelib/serialization/qcborvalue.h +++ b/src/corelib/serialization/qcborvalue.h @@ -198,6 +198,7 @@ public: QByteArray toByteArray(const QByteArray &defaultValue = {}) const; QString toString(const QString &defaultValue = {}) const; + QAnyStringView toStringView(QAnyStringView defaultValue = {}) const; #if QT_CONFIG(datestring) QDateTime toDateTime(const QDateTime &defaultValue = {}) const; #endif @@ -350,6 +351,8 @@ public: { return concrete().toByteArray(defaultValue); } QString toString(const QString &defaultValue = {}) const { return concrete().toString(defaultValue); } + QAnyStringView toStringView(QAnyStringView defaultValue = {}) const + { return concreteStringView(*this, defaultValue); } #if QT_CONFIG(datestring) QDateTime toDateTime(const QDateTime &defaultValue = {}) const { return concrete().toDateTime(defaultValue); } @@ -442,6 +445,8 @@ protected: concreteByteArray(QCborValueConstRef that, const QByteArray &defaultValue); static Q_CORE_EXPORT QString concreteString(QCborValueConstRef that, const QString &defaultValue); + static Q_CORE_EXPORT QAnyStringView + concreteStringView(QCborValueConstRef that, QAnyStringView defaultValue); constexpr QCborValueConstRef() : d(nullptr), i(0) {} // this will actually be invalid constexpr QCborValueConstRef(QCborContainerPrivate *dd, qsizetype ii) diff --git a/src/corelib/serialization/qjsonvalue.cpp b/src/corelib/serialization/qjsonvalue.cpp index 80115e77b65..71934932b26 100644 --- a/src/corelib/serialization/qjsonvalue.cpp +++ b/src/corelib/serialization/qjsonvalue.cpp @@ -744,12 +744,34 @@ double QJsonValue::toDouble(double defaultValue) const Converts the value to a QString and returns it. If type() is not String, the \a defaultValue will be returned. + + \sa toStringView() */ QString QJsonValue::toString(const QString &defaultValue) const { return value.toString(defaultValue); } +/*! + \since 6.10 + + Returns the string value stored in this QJsonValue, if it is of the + \l{String}{string} type. Otherwise, it returns \a defaultValue. Since + QJsonValue stores strings in either US-ASCII, UTF-8 or UTF-16, the returned + QAnyStringView may be in any of these encodings. + + This function does not allocate memory. The return value is valid until the + next call to a non-const member function on this object. If this object goes + out of scope, the return value is valid until the next call to a non-const + member function on the parent JSON object or array. + + \sa toString() +*/ +QAnyStringView QJsonValue::toStringView(QAnyStringView defaultValue) const +{ + return value.toStringView(defaultValue); +} + /*! Converts the value to a QString and returns it. @@ -1066,6 +1088,15 @@ QString QJsonValueConstRef::concreteString(QJsonValueConstRef self, const QStrin return d->stringAt(index); } +QAnyStringView QJsonValueConstRef::concreteStringView(QJsonValueConstRef self, QAnyStringView defaultValue) +{ + const QCborContainerPrivate *d = QJsonPrivate::Value::container(self); + const qsizetype index = QJsonPrivate::Value::indexHelper(self); + if (d->elements.at(index).type != QCborValue::String) + return defaultValue; + return d->anyStringViewAt(index); +} + QJsonValue QJsonValueConstRef::concrete(QJsonValueConstRef self) noexcept { const QCborContainerPrivate *d = QJsonPrivate::Value::container(self); diff --git a/src/corelib/serialization/qjsonvalue.h b/src/corelib/serialization/qjsonvalue.h index a96d57890db..cb56f38b1a4 100644 --- a/src/corelib/serialization/qjsonvalue.h +++ b/src/corelib/serialization/qjsonvalue.h @@ -100,6 +100,7 @@ public: double toDouble(double defaultValue = 0) const; QString toString() const; QString toString(const QString &defaultValue) const; + QAnyStringView toStringView(QAnyStringView defaultValue = {}) const; QJsonArray toArray() const; QJsonArray toArray(const QJsonArray &defaultValue) const; QJsonObject toObject() const; @@ -165,6 +166,8 @@ public: { return concreteDouble(*this, defaultValue); } QString toString(const QString &defaultValue = {}) const { return concreteString(*this, defaultValue); } + QAnyStringView toStringView(QAnyStringView defaultValue = {}) const + { return concreteStringView(*this, defaultValue); } Q_CORE_EXPORT QJsonArray toArray() const; Q_CORE_EXPORT QJsonObject toObject() const; @@ -195,6 +198,7 @@ protected: Q_CORE_EXPORT static double concreteDouble(QJsonValueConstRef self, double defaultValue) noexcept Q_DECL_PURE_FUNCTION; Q_CORE_EXPORT static QString concreteString(QJsonValueConstRef self, const QString &defaultValue); + Q_CORE_EXPORT static QAnyStringView concreteStringView(QJsonValueConstRef self, QAnyStringView defaultValue); Q_CORE_EXPORT static QJsonValue concrete(QJsonValueConstRef self) noexcept; // for iterators @@ -287,6 +291,8 @@ public: inline qint64 toInteger(qint64 defaultValue = 0) const { return QJsonValueConstRef::toInteger(defaultValue); } inline double toDouble(double defaultValue = 0) const { return QJsonValueConstRef::toDouble(defaultValue); } inline QString toString(const QString &defaultValue = {}) const { return QJsonValueConstRef::toString(defaultValue); } + QAnyStringView toStringView(QAnyStringView defaultValue = {}) const + { return QJsonValueConstRef::toStringView(defaultValue); } QJsonArray toArray() const; QJsonObject toObject() const; diff --git a/src/gui/opengl/qopengl.cpp b/src/gui/opengl/qopengl.cpp index 587975085ad..021c3964219 100644 --- a/src/gui/opengl/qopengl.cpp +++ b/src/gui/opengl/qopengl.cpp @@ -176,8 +176,8 @@ VersionTerm VersionTerm::fromJson(const QJsonValue &v) if (!v.isObject()) return result; const QJsonObject o = v.toObject(); - result.number = QVersionNumber::fromString(o.value("value"_L1).toString()); - const QString opS = o.value("op"_L1).toString(); + result.number = QVersionNumber::fromString(o.value("value"_L1).toStringView()); + const auto opS = o.value("op"_L1).toStringView(); for (size_t i = 0; i < sizeof(operators) / sizeof(operators[0]); ++i) { if (opS == QLatin1StringView(operators[i])) { result.op = static_cast(i); diff --git a/src/platformsupport/kmsconvenience/qkmsdevice.cpp b/src/platformsupport/kmsconvenience/qkmsdevice.cpp index e49022d06ec..cdd2ac2d572 100644 --- a/src/platformsupport/kmsconvenience/qkmsdevice.cpp +++ b/src/platformsupport/kmsconvenience/qkmsdevice.cpp @@ -1044,14 +1044,15 @@ void QKmsScreenConfig::loadConfig() m_devicePath = object.value("device"_L1).toString(); m_separateScreens = object.value("separateScreens"_L1).toBool(m_separateScreens); - const QString vdOriString = object.value("virtualDesktopLayout"_L1).toString(); + const auto vdOriString = object.value("virtualDesktopLayout"_L1).toStringView(); if (!vdOriString.isEmpty()) { if (vdOriString == "horizontal"_L1) m_virtualDesktopLayout = VirtualDesktopLayoutHorizontal; else if (vdOriString == "vertical"_L1) m_virtualDesktopLayout = VirtualDesktopLayoutVertical; else - qCWarning(qLcKmsDebug) << "Unknown virtualDesktopOrientation value" << vdOriString; + qCWarning(qLcKmsDebug, "Unknown virtualDesktopOrientation value %ls", + qUtf16Printable(vdOriString.toString())); } const QJsonArray outputs = object.value("outputs"_L1).toArray(); diff --git a/tests/auto/corelib/serialization/qcborvalue/tst_qcborvalue.cpp b/tests/auto/corelib/serialization/qcborvalue/tst_qcborvalue.cpp index 5dee0a8d4e9..90b9679309c 100644 --- a/tests/auto/corelib/serialization/qcborvalue/tst_qcborvalue.cpp +++ b/tests/auto/corelib/serialization/qcborvalue/tst_qcborvalue.cpp @@ -342,6 +342,7 @@ static void basicTypeCheck(QCborValue::Type type, const QCborValue &v, const QVa QVERIFY(validexpr) CMP(v.toByteArray(), QByteArray, v.toByteArray().isNull()); CMP(v.toString(), QString, v.toString().isNull()); + CMP(v.toStringView(), QString, v.toStringView().isNull()); CMP(v.toDateTime(), QDateTime, !v.toDateTime().isValid()); CMP(v.toUrl(), QUrl, !v.toUrl().isValid()); CMP(v.toRegularExpression(), QRegularExpression, v.toRegularExpression().pattern().isNull()); @@ -886,6 +887,7 @@ void tst_QCborValue::mapSimpleInitializerList() QCborValue v = m.value(2); QVERIFY(v.isString()); QCOMPARE(v.toString(), "Hello"); + QCOMPARE(v.toStringView(), u8"Hello"); QCOMPARE(vmap[2], v); } { @@ -893,6 +895,7 @@ void tst_QCborValue::mapSimpleInitializerList() QCborValue v = m.value(3); QVERIFY(v.isString()); QCOMPARE(v.toString(), "World"); + QCOMPARE(v.toStringView(), "World"_L1); QCOMPARE(vmap[3], v); } { @@ -934,6 +937,7 @@ void tst_QCborValue::mapSimpleInitializerList() QCOMPARE((it + 1).key(), QCborValue(2)); QVERIFY((it + 1)->isString()); QCOMPARE((it + 1)->toString(), "Hello"); + QCOMPARE((it + 1)->toStringView(), u8"Hello"); it += 2; QCOMPARE(it.key(), QCborValue("Hello")); QVERIFY(it->isInteger()); @@ -941,6 +945,7 @@ void tst_QCborValue::mapSimpleInitializerList() QCOMPARE(it.key(), QCborValue(3)); QVERIFY(it->isString()); QCOMPARE(it.value().toString(), "World"); + QCOMPARE(it.value().toStringView(), "World"_L1); --end; QCOMPARE(end.key(), QCborValue("World")); QCOMPARE(end.value(), QCborValue(3)); @@ -1462,6 +1467,7 @@ void tst_QCborValue::arrayStringElements() QCborValueRef r1 = a[0]; QCOMPARE(r1.toString(), "Hello"); + QCOMPARE(r1.toStringView(), u8"Hello"); QCOMPARE(r1.operator QCborValue(), QCborValue("Hello")); QT_TEST_EQUALITY_OPS(r1, QCborValue("Hello"), true); @@ -1476,10 +1482,11 @@ void tst_QCborValue::arrayStringElements() v2 = a.at(1); QCOMPARE(v2.toString(), "World"); + QCOMPARE(v2.toStringView(), u8"World"); QT_TEST_EQUALITY_OPS(v2, QCborValue("World"), true); QCOMPARE(a.takeAt(1).toString(), "World"); - QCOMPARE(a.takeAt(0).toString(), "Hello"); + QCOMPARE(a.takeAt(0).toStringView(), "Hello"_L1); QVERIFY(a.isEmpty()); } @@ -1492,6 +1499,7 @@ void tst_QCborValue::mapStringValues() QCborValueRef r1 = m[0]; QCOMPARE(r1.toString(), "Hello"); + QCOMPARE(r1.toStringView(), "Hello"_L1); QCOMPARE(r1.operator QCborValue(), QCborValue("Hello")); QT_TEST_EQUALITY_OPS(r1, QCborValue("Hello"), true); @@ -1506,10 +1514,11 @@ void tst_QCborValue::mapStringValues() v2 = (m.begin() + 1).value(); QCOMPARE(v2.toString(), "World"); + QCOMPARE(v2.toStringView(), "World"_L1); QCOMPARE(v2, QCborValue("World")); QCOMPARE(m.extract(m.begin() + 1).toString(), "World"); - QCOMPARE(m.take(0).toString(), "Hello"); + QCOMPARE(m.take(0).toStringView(), u8"Hello"); QVERIFY(m.isEmpty()); } @@ -1530,6 +1539,7 @@ void tst_QCborValue::mapStringKeys() QVERIFY(m2.value(QCborValue(QByteArray("foo"))).isUndefined()); QVERIFY(m.value(QCborValue(QLatin1String("foo"))).isUndefined()); QCOMPARE(m.value(QCborValue(QByteArray("foo"))).toString(), "bar"); + QCOMPARE(m.value(QCborValue(QByteArray("foo"))).toStringView(), "bar"); m.insert(u"World"_s, QCborValue(3)); // replaces QCOMPARE(m.size(), 3); @@ -1796,7 +1806,7 @@ void tst_QCborValue::mapComplexKeys() // basics_data() strings are Latin1 QByteArray latin1 = v.toString().toLatin1(); - Q_ASSERT(v.toString() == QString::fromLatin1(latin1)); + Q_ASSERT(v.toStringView() == QString::fromLatin1(latin1)); QCOMPARE(m[QLatin1String(latin1)].toInteger(), 42); } @@ -3007,6 +3017,7 @@ template static void cborValueRef_template() QCOMPARE(ref.toDouble(47), v.toDouble(47)); QCOMPARE(ref.toByteArray("other"), v.toByteArray("other")); QCOMPARE(ref.toString("other"), v.toString("other")); + QCOMPARE(ref.toStringView("other"_L1), v.toStringView("other")); QCOMPARE(ref.toArray(otherArray), v.toArray(otherArray)); QCOMPARE(ref.toMap(otherMap), v.toMap(otherMap)); QCOMPARE(ref.toDateTime(otherDateTime), v.toDateTime(otherDateTime)); diff --git a/tests/benchmarks/corelib/json/tst_bench_qtjson.cpp b/tests/benchmarks/corelib/json/tst_bench_qtjson.cpp index eff1b4cf91b..f6fc7a0f6f3 100644 --- a/tests/benchmarks/corelib/json/tst_bench_qtjson.cpp +++ b/tests/benchmarks/corelib/json/tst_bench_qtjson.cpp @@ -38,6 +38,17 @@ private Q_SLOTS: [](auto it) { return it->toString(); }); } + void iteratorKeyValueView() + { + iteratorKeyImpl([](auto it) { return it.key(); }, + [](auto it) { return it->toStringView(); }); + } + void iteratorKeyViewValueView() + { + iteratorKeyImpl([](auto it) { return it.keyView(); }, + [](auto it) { return it->toStringView(); }); + } + private: template void iteratorKeyImpl(KeyFunc key, ValueFunc val);