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:
Marc Mutz 2022-02-22 21:27:37 +01:00
parent 03de9ff7ee
commit 6ec1dc904d
3 changed files with 72 additions and 45 deletions

View File

@ -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");

View File

@ -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);

View File

@ -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