-
Notifications
You must be signed in to change notification settings - Fork 14.3k
[libc++] Optimize char_traits a bit #72799
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
@llvm/pr-subscribers-libcxx Author: None (philnik777) ChangesThis implements two kinds of optimizations. Specifically
Full diff: https://github.com/llvm/llvm-project/pull/72799.diff 2 Files Affected:
diff --git a/libcxx/include/__string/char_traits.h b/libcxx/include/__string/char_traits.h
index 17eade2f8188f76..b3bde455eb4a260 100644
--- a/libcxx/include/__string/char_traits.h
+++ b/libcxx/include/__string/char_traits.h
@@ -11,12 +11,14 @@
#include <__algorithm/copy_n.h>
#include <__algorithm/fill_n.h>
+#include <__algorithm/find.h>
#include <__algorithm/find_end.h>
#include <__algorithm/find_first_of.h>
#include <__algorithm/min.h>
#include <__compare/ordering.h>
#include <__config>
#include <__functional/hash.h>
+#include <__functional/identity.h>
#include <__iterator/iterator_traits.h>
#include <__string/constexpr_c_functions.h>
#include <__type_traits/is_constant_evaluated.h>
@@ -355,34 +357,35 @@ struct _LIBCPP_TEMPLATE_VIS char_traits<char8_t>
{return __c1 < __c2;}
static _LIBCPP_HIDE_FROM_ABI constexpr int
- compare(const char_type* __s1, const char_type* __s2, size_t __n) _NOEXCEPT {
+ compare(const char_type* __s1, const char_type* __s2, size_t __n) noexcept {
return std::__constexpr_memcmp(__s1, __s2, __element_count(__n));
}
- static _LIBCPP_HIDE_FROM_ABI constexpr
- size_t length(const char_type* __s) _NOEXCEPT;
+ static _LIBCPP_HIDE_FROM_ABI constexpr size_t length(const char_type* __str) noexcept {
+ return std::__constexpr_strlen(__str);
+ }
- _LIBCPP_INLINE_VISIBILITY static constexpr
- const char_type* find(const char_type* __s, size_t __n, const char_type& __a) _NOEXCEPT;
+ static _LIBCPP_HIDE_FROM_ABI constexpr const char_type*
+ find(const char_type* __s, size_t __n, const char_type& __a) noexcept {
+ return std::__constexpr_memchr(__s, __a, __n);
+ }
- static _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20
- char_type* move(char_type* __s1, const char_type* __s2, size_t __n) _NOEXCEPT {
- return std::__constexpr_memmove(__s1, __s2, __element_count(__n));
- }
+ static _LIBCPP_HIDE_FROM_ABI constexpr char_type*
+ move(char_type* __s1, const char_type* __s2, size_t __n) noexcept {
+ return std::__constexpr_memmove(__s1, __s2, __element_count(__n));
+ }
- static _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20
- char_type* copy(char_type* __s1, const char_type* __s2, size_t __n) _NOEXCEPT {
- _LIBCPP_ASSERT_NON_OVERLAPPING_RANGES(!std::__is_pointer_in_range(__s1, __s1 + __n, __s2),
- "char_traits::copy: source and destination ranges overlap");
- std::copy_n(__s2, __n, __s1);
- return __s1;
- }
+ static _LIBCPP_HIDE_FROM_ABI constexpr char_type* copy(char_type* __s1, const char_type* __s2, size_t __n) noexcept {
+ _LIBCPP_ASSERT_NON_OVERLAPPING_RANGES(!std::__is_pointer_in_range(__s1, __s1 + __n, __s2),
+ "char_traits::copy: source and destination ranges overlap");
+ std::copy_n(__s2, __n, __s1);
+ return __s1;
+ }
- static _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20
- char_type* assign(char_type* __s, size_t __n, char_type __a) _NOEXCEPT {
- std::fill_n(__s, __n, __a);
- return __s;
- }
+ static _LIBCPP_HIDE_FROM_ABI constexpr char_type* assign(char_type* __s, size_t __n, char_type __a) noexcept {
+ std::fill_n(__s, __n, __a);
+ return __s;
+ }
static inline _LIBCPP_HIDE_FROM_ABI constexpr int_type not_eof(int_type __c) noexcept
{return eq_int_type(__c, eof()) ? ~eof() : __c;}
@@ -396,31 +399,6 @@ struct _LIBCPP_TEMPLATE_VIS char_traits<char8_t>
{return int_type(EOF);}
};
-// TODO use '__builtin_strlen' if it ever supports char8_t ??
-inline constexpr
-size_t
-char_traits<char8_t>::length(const char_type* __s) _NOEXCEPT
-{
- size_t __len = 0;
- for (; !eq(*__s, char_type(0)); ++__s)
- ++__len;
- return __len;
-}
-
-// TODO use '__builtin_char_memchr' if it ever supports char8_t ??
-inline constexpr
-const char8_t*
-char_traits<char8_t>::find(const char_type* __s, size_t __n, const char_type& __a) _NOEXCEPT
-{
- for (; __n; --__n)
- {
- if (eq(*__s, __a))
- return __s;
- ++__s;
- }
- return nullptr;
-}
-
#endif // _LIBCPP_HAS_NO_CHAR8_T
template <>
@@ -446,8 +424,15 @@ struct _LIBCPP_TEMPLATE_VIS char_traits<char16_t>
int compare(const char_type* __s1, const char_type* __s2, size_t __n) _NOEXCEPT;
_LIBCPP_INLINE_VISIBILITY static _LIBCPP_CONSTEXPR_SINCE_CXX17
size_t length(const char_type* __s) _NOEXCEPT;
- _LIBCPP_INLINE_VISIBILITY static _LIBCPP_CONSTEXPR_SINCE_CXX17
- const char_type* find(const char_type* __s, size_t __n, const char_type& __a) _NOEXCEPT;
+
+ static _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX17 const char_type*
+ find(const char_type* __s, size_t __n, const char_type& __a) _NOEXCEPT {
+ __identity __proj;
+ const char_type* __match = std::__find_impl(__s, __s + __n, __a, __proj);
+ if (__match == __s + __n)
+ return nullptr;
+ return __match;
+ }
_LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_SINCE_CXX20
static char_type* move(char_type* __s1, const char_type* __s2, size_t __n) _NOEXCEPT {
@@ -504,19 +489,6 @@ char_traits<char16_t>::length(const char_type* __s) _NOEXCEPT
return __len;
}
-inline _LIBCPP_CONSTEXPR_SINCE_CXX17
-const char16_t*
-char_traits<char16_t>::find(const char_type* __s, size_t __n, const char_type& __a) _NOEXCEPT
-{
- for (; __n; --__n)
- {
- if (eq(*__s, __a))
- return __s;
- ++__s;
- }
- return nullptr;
-}
-
template <>
struct _LIBCPP_TEMPLATE_VIS char_traits<char32_t>
{
@@ -540,8 +512,15 @@ struct _LIBCPP_TEMPLATE_VIS char_traits<char32_t>
int compare(const char_type* __s1, const char_type* __s2, size_t __n) _NOEXCEPT;
_LIBCPP_INLINE_VISIBILITY static _LIBCPP_CONSTEXPR_SINCE_CXX17
size_t length(const char_type* __s) _NOEXCEPT;
- _LIBCPP_INLINE_VISIBILITY static _LIBCPP_CONSTEXPR_SINCE_CXX17
- const char_type* find(const char_type* __s, size_t __n, const char_type& __a) _NOEXCEPT;
+
+ static _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX17 const char_type*
+ find(const char_type* __s, size_t __n, const char_type& __a) _NOEXCEPT {
+ __identity __proj;
+ const char_type* __match = std::__find_impl(__s, __s + __n, __a, __proj);
+ if (__match == __s + __n)
+ return nullptr;
+ return __match;
+ }
_LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_SINCE_CXX20
static char_type* move(char_type* __s1, const char_type* __s2, size_t __n) _NOEXCEPT {
@@ -596,19 +575,6 @@ char_traits<char32_t>::length(const char_type* __s) _NOEXCEPT
return __len;
}
-inline _LIBCPP_CONSTEXPR_SINCE_CXX17
-const char32_t*
-char_traits<char32_t>::find(const char_type* __s, size_t __n, const char_type& __a) _NOEXCEPT
-{
- for (; __n; --__n)
- {
- if (eq(*__s, __a))
- return __s;
- ++__s;
- }
- return nullptr;
-}
-
// helper fns for basic_string and string_view
// __str_find
diff --git a/libcxx/include/__string/constexpr_c_functions.h b/libcxx/include/__string/constexpr_c_functions.h
index 198f0f5e6809147..c029c2d29fec88c 100644
--- a/libcxx/include/__string/constexpr_c_functions.h
+++ b/libcxx/include/__string/constexpr_c_functions.h
@@ -35,18 +35,23 @@ _LIBCPP_BEGIN_NAMESPACE_STD
// of elements as opposed to a number of bytes.
enum class __element_count : size_t {};
-inline _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 size_t __constexpr_strlen(const char* __str) {
+template <class _Tp>
+inline _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 size_t __constexpr_strlen(const _Tp* __str) _NOEXCEPT {
+ static_assert(
+ is_integral<_Tp>::value && sizeof(_Tp) == 1, "__constexpr_strlen only works with integral types of size 1");
// GCC currently doesn't support __builtin_strlen for heap-allocated memory during constant evaluation.
// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=70816
-#ifdef _LIBCPP_COMPILER_GCC
if (__libcpp_is_constant_evaluated()) {
+#if _LIBCPP_STD_VER >= 17 && defined(_LIBCPP_COMPILER_CLANG_BASED)
+ if constexpr (is_same_v<_Tp, char>)
+ return __builtin_strlen(__str);
+#endif
size_t __i = 0;
for (; __str[__i] != '\0'; ++__i)
;
return __i;
}
-#endif
- return __builtin_strlen(__str);
+ return __builtin_strlen(reinterpret_cast<const char*>(__str));
}
// Because of __libcpp_is_trivially_lexicographically_comparable we know that comparing the object representations is
|
Could you please provide information about: (A) Benchmark results for these functions. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As mentioned, I would like to see some data on the improvements.
7b104b2
to
03069d6
Compare
These are the numbers from optimizing
Since it's basically the same optimization I think these are pretty representative of the expected speedup. The numbers for
I don't know how to measure memory usage properly, but the time of including |
✅ With the latest revision this PR passed the C/C++ code formatter. |
✅ With the latest revision this PR passed the Python code formatter. |
This implements two kinds of optimizations. Specifically - `char_traits<char8_t>` uses `char` code paths; these are heavily optimized and the operations are equivalent - `char16_t` and `char32_t` `find` uses `std::find` to forward to `wmemchr` if they have the same size
03069d6
to
5e31d44
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM assuming we already have appropriate coverage in char_traits for char8_t
and friends.
_LIBCPP_HIDE_FROM_ABI static _LIBCPP_CONSTEXPR_SINCE_CXX17 const char_type* | ||
find(const char_type* __s, size_t __n, const char_type& __a) _NOEXCEPT { | ||
__identity __proj; | ||
const char_type* __match = std::__find_impl(__s, __s + __n, __a, __proj); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unrelated to this patch, but can we rename __find_impl
to __find
for consistency?
ping @EricWF |
Landing, since I addressed all the feedback. @EricWF if you have any more thoughts feel free to comment. |
This implements two kinds of optimizations. Specifically - `char_traits<char8_t>` uses `char` code paths; these are heavily optimized and the operations are equivalent - `char16_t` and `char32_t` `find` uses `std::find` to forward to `wmemchr` if they have the same size
This implements two kinds of optimizations. Specifically
char_traits<char8_t>
useschar
code paths; these are heavilyoptimized and the operations are equivalent
char16_t
andchar32_t
find
usesstd::find
to forward towmemchr
if they have the same size