From b1ee49b46533d39f7fabda68d0bd08a1ab130a27 Mon Sep 17 00:00:00 2001 From: Thiago Macieira Date: Fri, 5 May 2023 12:29:37 -0700 Subject: [PATCH] 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 Reviewed-by: Thiago Macieira --- src/corelib/text/qanystringview.h | 22 ++--------- src/corelib/text/qstring.cpp | 5 +++ src/corelib/text/qstringalgorithms.h | 56 +++++++++++++++++++++++++++- src/corelib/text/qstringview.h | 18 +-------- 4 files changed, 66 insertions(+), 35 deletions(-) diff --git a/src/corelib/text/qanystringview.h b/src/corelib/text/qanystringview.h index ac5cb54c78..2be79c2b52 100644 --- a/src/corelib/text/qanystringview.h +++ b/src/corelib/text/qanystringview.h @@ -147,20 +147,6 @@ private: return qsizetype(strlen(reinterpret_cast(str))); } - template - static constexpr qsizetype lengthHelperContainer(const Container &c) noexcept - { - return qsizetype(std::size(c)); - } - - template - static constexpr qsizetype lengthHelperContainer(const Char (&str)[N]) noexcept - { - const auto it = std::char_traits::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 = 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 = true> constexpr QAnyStringView(Container &&c, QtPrivate::wrapped_t &&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 constexpr QAnyStringView(QBasicUtf8StringView v) noexcept - : QAnyStringView(std::data(v), lengthHelperContainer(v)) {} + : QAnyStringView(std::data(v), QtPrivate::lengthHelperContainer(v)) {} template = true> [[nodiscard]] constexpr static QAnyStringView fromArray(const Char (&string)[Size]) noexcept diff --git a/src/corelib/text/qstring.cpp b/src/corelib/text/qstring.cpp index 1fae9ef07a..15cf40ef88 100644 --- a/src/corelib/text/qstring.cpp +++ b/src/corelib/text/qstring.cpp @@ -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 * diff --git a/src/corelib/text/qstringalgorithms.h b/src/corelib/text/qstringalgorithms.h index 888bad5dd3..a8d43b3ed0 100644 --- a/src/corelib/text/qstringalgorithms.h +++ b/src/corelib/text/qstringalgorithms.h @@ -11,11 +11,15 @@ #pragma qt_class(QStringAlgorithms) #endif +#include // std::find +#include // 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 [[nodiscard]] constexpr Q_ALWAYS_INLINE +std::enable_if_t +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(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::find(str, N, Char(0)); + return it ? std::distance(str, it) : ptrdiff_t(N); +#endif +} + +template [[nodiscard]] constexpr inline +std::enable_if_t lengthHelperContainer(const Char (&str)[N]) +{ + // std::char_traits::find will call memchr or __builtin_memchr for us + const auto it = std::char_traits::find(str, N, Char(0)); + return it ? std::distance(str, it) : ptrdiff_t(N); +} + +template +constexpr qsizetype lengthHelperContainer(const Container &c) noexcept +{ + return qsizetype(std::size(c)); +} +} // namespace QtPrivate QT_END_NAMESPACE diff --git a/src/corelib/text/qstringview.h b/src/corelib/text/qstringview.h index e4c0c4dd8d..a6f217d2a5 100644 --- a/src/corelib/text/qstringview.h +++ b/src/corelib/text/qstringview.h @@ -116,20 +116,6 @@ private: return QtPrivate::qustrlen(reinterpret_cast(str)); } - template - static constexpr qsizetype lengthHelperContainer(const Container &c) noexcept - { - return qsizetype(std::size(c)); - } - - template - static constexpr qsizetype lengthHelperContainer(const Char (&str)[N]) noexcept - { - const auto it = std::char_traits::find(str, N, Char(0)); - const auto end = it ? it : std::end(str); - return qsizetype(std::distance(str, end)); - } - template static const storage_type *castHelper(const Char *str) noexcept { return reinterpret_cast(str); } @@ -178,8 +164,8 @@ public: #endif template = 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 = true> [[nodiscard]] constexpr static QStringView fromArray(const Char (&string)[Size]) noexcept