QSettings: port key processing to QAnyStringView
... in preparation for replacing the QString keys in the public API with QAnyStringView ones. This removes the "important optimization" that avoids a detach in the common case where the input is the same as the output of normalization. But that optimization is beside the point, because it trades a memory allocation avoided in the library for O(N) allocations inserted into user code for each call to QSettings::value(), the vast majority of which are calls with string literals. With the public interface ported to QAnyStringView in the follow-up patch, we can then internally optimize memory allocations _in a central place_ (e.g. by returning std::u16string or QVarLengthArray<QChar> from normalizeKey() instead of QString). But first we need to get rid of all the unwarranted allocations in user code. Change-Id: I45fc83d972c552a220c9c29508001d3f172e1162 Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org> Reviewed-by: Edward Welbourne <edward.welbourne@qt.io> Reviewed-by: Sona Kurazyan <sona.kurazyan@qt.io>
This commit is contained in:
parent
03de9ff7ee
commit
6ec1dc904d
@ -1,6 +1,6 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2020 The Qt Company Ltd.
|
||||
** Copyright (C) 2022 The Qt Company Ltd.
|
||||
** Contact: https://www.qt.io/licensing/
|
||||
**
|
||||
** This file is part of the QtCore module of the Qt Toolkit.
|
||||
@ -53,7 +53,7 @@
|
||||
#include "qtemporaryfile.h"
|
||||
#include "qstandardpaths.h"
|
||||
#include <qdatastream.h>
|
||||
#include <qstringconverter.h>
|
||||
#include "private/qstringconverter_p.h"
|
||||
|
||||
#ifndef QT_NO_GEOM_VARIANT
|
||||
#include "qsize.h"
|
||||
@ -230,13 +230,32 @@ QSettingsPrivate::~QSettingsPrivate()
|
||||
{
|
||||
}
|
||||
|
||||
QString QSettingsPrivate::actualKey(const QString &key) const
|
||||
QString QSettingsPrivate::actualKey(QAnyStringView key) const
|
||||
{
|
||||
auto n = normalizedKey(key);
|
||||
Q_ASSERT_X(!n.isEmpty(), "QSettings", "empty key");
|
||||
return groupPrefix + n;
|
||||
}
|
||||
|
||||
namespace {
|
||||
// ### this needs some public API (QStringConverter?)
|
||||
QChar *write(QChar *out, QUtf8StringView v)
|
||||
{
|
||||
return QUtf8::convertToUnicode(out, QByteArrayView(v));
|
||||
}
|
||||
QChar *write(QChar *out, QLatin1String v)
|
||||
{
|
||||
for (char ch : v)
|
||||
*out++ = QLatin1Char(ch);
|
||||
return out;
|
||||
}
|
||||
QChar *write(QChar *out, QStringView v)
|
||||
{
|
||||
memcpy(out, v.data(), v.size() * sizeof(QChar));
|
||||
return out + v.size();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Returns a string that never starts nor ends with a slash (or an
|
||||
empty string). Examples:
|
||||
@ -244,32 +263,43 @@ QString QSettingsPrivate::actualKey(const QString &key) const
|
||||
"foo" becomes "foo"
|
||||
"/foo//bar///" becomes "foo/bar"
|
||||
"///" becomes ""
|
||||
|
||||
This function is optimized to avoid a QString deep copy in the
|
||||
common case where the key is already normalized.
|
||||
*/
|
||||
QString QSettingsPrivate::normalizedKey(const QString &key)
|
||||
QString QSettingsPrivate::normalizedKey(QAnyStringView key)
|
||||
{
|
||||
QString result = key;
|
||||
QString result(key.size(), Qt::Uninitialized);
|
||||
auto out = const_cast<QChar*>(result.constData()); // don't detach
|
||||
|
||||
int i = 0;
|
||||
while (i < result.size()) {
|
||||
while (result.at(i) == QLatin1Char('/')) {
|
||||
result.remove(i, 1);
|
||||
if (i == result.size())
|
||||
goto after_loop;
|
||||
}
|
||||
while (result.at(i) != QLatin1Char('/')) {
|
||||
++i;
|
||||
if (i == result.size())
|
||||
return result;
|
||||
}
|
||||
++i; // leave the slash alone
|
||||
}
|
||||
const bool maybeEndsInSlash = key.visit([&out](auto key) {
|
||||
using View = decltype(key);
|
||||
|
||||
after_loop:
|
||||
if (!result.isEmpty())
|
||||
result.truncate(i - 1); // remove the trailing slash
|
||||
auto it = key.begin();
|
||||
const auto end = key.end();
|
||||
|
||||
while (it != end) {
|
||||
while (*it == u'/') {
|
||||
++it;
|
||||
if (it == end)
|
||||
return true;
|
||||
}
|
||||
auto mark = it;
|
||||
while (*it != u'/') {
|
||||
++it;
|
||||
if (it == end)
|
||||
break;
|
||||
}
|
||||
out = write(out, View{mark, it});
|
||||
if (it == end)
|
||||
return false;
|
||||
Q_ASSERT(*it == u'/');
|
||||
*out++ = u'/';
|
||||
++it;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
if (maybeEndsInSlash && out != result.constData())
|
||||
--out; // remove the trailing slash
|
||||
result.truncate(out - result.constData());
|
||||
return result;
|
||||
}
|
||||
|
||||
@ -3290,7 +3320,7 @@ QVariant QSettings::value(const QString &key, const QVariant &defaultValue) cons
|
||||
return d->value(key, &defaultValue);
|
||||
}
|
||||
|
||||
QVariant QSettingsPrivate::value(const QString &key, const QVariant *defaultValue) const
|
||||
QVariant QSettingsPrivate::value(QAnyStringView key, const QVariant *defaultValue) const
|
||||
{
|
||||
if (key.isEmpty()) {
|
||||
qWarning("QSettings::value: Empty key passed");
|
||||
|
@ -1,6 +1,6 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2016 The Qt Company Ltd.
|
||||
** Copyright (C) 2022 The Qt Company Ltd.
|
||||
** Contact: https://www.qt.io/licensing/
|
||||
**
|
||||
** This file is part of the QtCore module of the Qt Toolkit.
|
||||
@ -209,14 +209,14 @@ public:
|
||||
virtual bool isWritable() const = 0;
|
||||
virtual QString fileName() const = 0;
|
||||
|
||||
QVariant value(const QString &key, const QVariant *defaultValue) const;
|
||||
QString actualKey(const QString &key) const;
|
||||
QVariant value(QAnyStringView key, const QVariant *defaultValue) const;
|
||||
QString actualKey(QAnyStringView key) const;
|
||||
void beginGroupOrArray(const QSettingsGroup &group);
|
||||
void setStatus(QSettings::Status status) const;
|
||||
void requestUpdate();
|
||||
void update();
|
||||
|
||||
static QString normalizedKey(const QString &key);
|
||||
static QString normalizedKey(QAnyStringView key);
|
||||
static QSettingsPrivate *create(QSettings::Format format, QSettings::Scope scope,
|
||||
const QString &organization, const QString &application);
|
||||
static QSettingsPrivate *create(const QString &fileName, QSettings::Format format);
|
||||
|
@ -1,6 +1,6 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2016 The Qt Company Ltd.
|
||||
** Copyright (C) 2022 The Qt Company Ltd.
|
||||
** Contact: https://www.qt.io/licensing/
|
||||
**
|
||||
** This file is part of the test suite of the Qt Toolkit.
|
||||
@ -2160,20 +2160,17 @@ void tst_QSettings::testNormalizedKey()
|
||||
|
||||
inKey.detach();
|
||||
|
||||
QString result = QSettingsPrivate::normalizedKey(inKey);
|
||||
QCOMPARE(result, outKey);
|
||||
|
||||
/*
|
||||
If the key is already normalized, we verify that outKey is
|
||||
just a shallow copy of the input string. This is an important
|
||||
optimization that shouldn't be removed accidentally.
|
||||
*/
|
||||
if (inKey == outKey) {
|
||||
QVERIFY(!result.isDetached());
|
||||
} else {
|
||||
if (!result.isEmpty()) {
|
||||
QVERIFY(result.isDetached());
|
||||
}
|
||||
{
|
||||
auto result = QSettingsPrivate::normalizedKey(inKey);
|
||||
QCOMPARE(result, outKey);
|
||||
}
|
||||
{
|
||||
auto result = QSettingsPrivate::normalizedKey(QUtf8StringView{inKey.toUtf8()});
|
||||
QCOMPARE(result, outKey);
|
||||
}
|
||||
{
|
||||
auto result = QSettingsPrivate::normalizedKey(QLatin1String{inKey.toLatin1()});
|
||||
QCOMPARE(result, outKey);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
Loading…
x
Reference in New Issue
Block a user