Q{Any,}StringView: optimize lengthHelperContainer for the runtime

Deduplicate it in the process by moving to qstringalgorithms.h.

In non-constexpr contexts, both GCC and Clang were giving up in
pre-calculating the length if the UTF-16 string literal was too big. For
the old code, that was 14 and 16 characters respectively. That number
can be raised by adding some Q_ALWAYS_INLINE and (for GCC's case),
replacing std::char_traits::find() with std::find(). If that limit is
exceeded, we call the newly introduced qustrnlen() function.

qustrnlen() is just qustrchr(), like qstrnlen() is just memchr(). But it
is introduced as a separate function so we could change implementation
if we ever wished to, plus QStringView is only forward-declared at this
point.

Change-Id: Ieab617d69f3b4b54ab30fffd175c560d926db1c3
Reviewed-by: Ahmad Samir <a.samirh78@gmail.com>
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
bb10
Thiago Macieira 2023-05-05 12:29:37 -07:00
parent 95e6fac0a5
commit b1ee49b465
4 changed files with 66 additions and 35 deletions

View File

@ -147,20 +147,6 @@ private:
return qsizetype(strlen(reinterpret_cast<const char*>(str)));
}
template <typename Container>
static constexpr qsizetype lengthHelperContainer(const Container &c) noexcept
{
return qsizetype(std::size(c));
}
template <typename Char, size_t N>
static constexpr qsizetype lengthHelperContainer(const Char (&str)[N]) noexcept
{
const auto it = std::char_traits<Char>::find(str, N, Char(0));
const auto end = it ? it : std::next(str, N);
return qsizetype(std::distance(str, end));
}
static QChar toQChar(char ch) noexcept { return toQChar(QLatin1Char{ch}); } // we don't handle UTF-8 multibytes
static QChar toQChar(QChar ch) noexcept { return ch; }
static QChar toQChar(QLatin1Char ch) noexcept { return ch; }
@ -202,8 +188,8 @@ public:
inline constexpr QAnyStringView(QLatin1StringView str) noexcept;
template <typename Container, if_compatible_container<Container> = true>
constexpr QAnyStringView(const Container &c) noexcept
: QAnyStringView(std::data(c), lengthHelperContainer(c)) {}
constexpr Q_ALWAYS_INLINE QAnyStringView(const Container &c) noexcept
: QAnyStringView(std::data(c), QtPrivate::lengthHelperContainer(c)) {}
template <typename Container, if_convertible_to<QString, Container> = true>
constexpr QAnyStringView(Container &&c, QtPrivate::wrapped_t<Container, QString> &&capacity = {})
@ -227,11 +213,11 @@ public:
: QAnyStringView(capacity = QChar::fromUcs4(c)) {}
constexpr QAnyStringView(QStringView v) noexcept
: QAnyStringView(std::data(v), lengthHelperContainer(v)) {}
: QAnyStringView(std::data(v), QtPrivate::lengthHelperContainer(v)) {}
template <bool UseChar8T>
constexpr QAnyStringView(QBasicUtf8StringView<UseChar8T> v) noexcept
: QAnyStringView(std::data(v), lengthHelperContainer(v)) {}
: QAnyStringView(std::data(v), QtPrivate::lengthHelperContainer(v)) {}
template <typename Char, size_t Size, if_compatible_char<Char> = true>
[[nodiscard]] constexpr static QAnyStringView fromArray(const Char (&string)[Size]) noexcept

View File

@ -698,6 +698,11 @@ qsizetype QtPrivate::qustrlen(const char16_t *str) noexcept
return result;
}
qsizetype QtPrivate::qustrnlen(const char16_t *str, qsizetype maxlen) noexcept
{
return qustrchr({ str, maxlen }, u'\0') - str;
}
/*!
* \internal
*

View File

@ -11,11 +11,15 @@
#pragma qt_class(QStringAlgorithms)
#endif
#include <algorithm> // std::find
#include <string> // std::char_traits
QT_BEGIN_NAMESPACE
namespace QtPrivate {
[[nodiscard]] Q_CORE_EXPORT Q_DECL_PURE_FUNCTION qsizetype qustrlen(const char16_t *str) noexcept;
[[nodiscard]] Q_CORE_EXPORT Q_DECL_PURE_FUNCTION qsizetype qustrnlen(const char16_t *str, qsizetype maxlen) noexcept;
[[nodiscard]] Q_CORE_EXPORT Q_DECL_PURE_FUNCTION const char16_t *qustrchr(QStringView str, char16_t ch) noexcept;
[[nodiscard]] Q_CORE_EXPORT Q_DECL_PURE_FUNCTION int compareStrings(QStringView lhs, QStringView rhs, Qt::CaseSensitivity cs = Qt::CaseSensitive) noexcept;
@ -114,7 +118,57 @@ namespace QtPrivate {
[[nodiscard]] Q_CORE_EXPORT Q_DECL_PURE_FUNCTION bool isLatin1(QStringView s) noexcept;
[[nodiscard]] Q_CORE_EXPORT Q_DECL_PURE_FUNCTION bool isValidUtf16(QStringView s) noexcept;
} // namespace QtPRivate
template <typename Char, size_t N> [[nodiscard]] constexpr Q_ALWAYS_INLINE
std::enable_if_t<sizeof(Char) == sizeof(char16_t), qsizetype>
lengthHelperContainer(const Char (&str)[N])
{
// The following values were empirically determined to detect the threshold
// at which the compiler gives up pre-calculating the std::find() below and
// instead inserts code to be executed at runtime.
constexpr size_t RuntimeThreshold =
#if defined(Q_CC_CLANG) // tested through Clang 16.0.0
100
#elif defined(Q_CC_GNU) // tested through GCC 13.1 at -O3 compilation level
__cplusplus >= 202002L ? 39 : 17
#else
0
#endif
;
if constexpr (N == 1) {
return str[0] == Char(0) ? 0 : 1;
} else if constexpr (N > RuntimeThreshold) {
#ifdef QT_SUPPORTS_IS_CONSTANT_EVALUATED
if (!qIsConstantEvaluated())
return QtPrivate::qustrnlen(reinterpret_cast<const char16_t *>(str), N);
#endif
}
// libstdc++'s std::find_if yields a higher threshold than
// std::char_traits::find
#if __cplusplus >= 202002 && defined(__cpp_lib_constexpr_algorithms)
const auto it = std::find(str, str + N, Char(0));
return it - str;
#else
const auto it = std::char_traits<Char>::find(str, N, Char(0));
return it ? std::distance(str, it) : ptrdiff_t(N);
#endif
}
template <typename Char, size_t N> [[nodiscard]] constexpr inline
std::enable_if_t<sizeof(Char) == 1, qsizetype> lengthHelperContainer(const Char (&str)[N])
{
// std::char_traits::find will call memchr or __builtin_memchr for us
const auto it = std::char_traits<Char>::find(str, N, Char(0));
return it ? std::distance(str, it) : ptrdiff_t(N);
}
template <typename Container>
constexpr qsizetype lengthHelperContainer(const Container &c) noexcept
{
return qsizetype(std::size(c));
}
} // namespace QtPrivate
QT_END_NAMESPACE

View File

@ -116,20 +116,6 @@ private:
return QtPrivate::qustrlen(reinterpret_cast<const char16_t *>(str));
}
template <typename Container>
static constexpr qsizetype lengthHelperContainer(const Container &c) noexcept
{
return qsizetype(std::size(c));
}
template <typename Char, size_t N>
static constexpr qsizetype lengthHelperContainer(const Char (&str)[N]) noexcept
{
const auto it = std::char_traits<Char>::find(str, N, Char(0));
const auto end = it ? it : std::end(str);
return qsizetype(std::distance(str, end));
}
template <typename Char>
static const storage_type *castHelper(const Char *str) noexcept
{ return reinterpret_cast<const storage_type*>(str); }
@ -178,8 +164,8 @@ public:
#endif
template <typename Container, if_compatible_container<Container> = true>
constexpr QStringView(const Container &c) noexcept
: QStringView(std::data(c), lengthHelperContainer(c)) {}
constexpr Q_ALWAYS_INLINE QStringView(const Container &c) noexcept
: QStringView(std::data(c), QtPrivate::lengthHelperContainer(c)) {}
template <typename Char, size_t Size, if_compatible_char<Char> = true>
[[nodiscard]] constexpr static QStringView fromArray(const Char (&string)[Size]) noexcept