From b307f44333437064287578879e67527bed770dca Mon Sep 17 00:00:00 2001 From: jeanlemotan Date: Wed, 3 Jul 2024 16:14:16 +0200 Subject: [PATCH] Small algorithm improvements to avoid allocations TL depends on cppcoro now --- CMakeLists.txt | 1 + include/tl/algorithm/string.h | 142 +++++++++++++--------------------- include/tl/ptr.h | 4 +- 3 files changed, 55 insertions(+), 92 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index aa58c00..5274d94 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -51,6 +51,7 @@ endforeach() add_library(TL STATIC ${SRC} ${HEADERS} ${NATVIS}) target_include_directories(TL PUBLIC "include") +target_include_directories(TL PUBLIC "${PROJECT_SOURCE_DIR}/../cppcoro/include") target_link_libraries(TL EASTL) #if(BUILD_SHARED_LIBS) diff --git a/include/tl/algorithm/string.h b/include/tl/algorithm/string.h index 94d720b..fbbc2ba 100644 --- a/include/tl/algorithm/string.h +++ b/include/tl/algorithm/string.h @@ -105,49 +105,7 @@ inline void to_upper_ascii(string& str) noexcept } template -tl::vector split_on_any(const String& str, const char* separators, empty_token_policy token_policy = empty_token_policy::discard) noexcept -{ - return split_on_any(str, String(separators), token_policy); -} - -template -tl::vector split_on_any(const String& str, const Delim& separators, empty_token_policy token_policy = empty_token_policy::discard) noexcept -{ - tl::vector dst; - dst.reserve(32); - split_on_any(str, - separators, - [&dst](const String& token) - { - dst.push_back(token); - }, - token_policy); - return dst; -} - -template -tl::vector split_on_all(const String& str, const Delim& separator, empty_token_policy token_policy = empty_token_policy::discard) noexcept -{ - tl::vector dst; - dst.reserve(32); - split_on_all(str, - separator, - [&dst](const String& token) - { - dst.push_back(token); - }, - token_policy); - return dst; -} - -template -void split_on_any(const String& str, const char* delimiters, const Func& functor, empty_token_policy token_policy = empty_token_policy::discard) noexcept -{ - return split_on_any(str, String(delimiters), functor, token_policy); -} - -template -void split_on_any(const String& str, char delimiter, const Func& functor, empty_token_policy token_policy = empty_token_policy::discard) noexcept +cppcoro::generator split_on_any(const String& str, char sep, empty_token_policy token_policy = empty_token_policy::discard) noexcept { String token; const char* cstr = str.data(); @@ -155,35 +113,39 @@ void split_on_any(const String& str, char delimiter, const Func& functor, empty_ const size_t endOffset = str.size(); while (offset <= endOffset) { - const char* nextCstr = static_cast(::memchr(cstr + offset, delimiter, endOffset - offset)); + const char* nextCstr = static_cast(::memchr(cstr + offset, sep, endOffset - offset)); const size_t nextOffset = nextCstr ? nextCstr - cstr : endOffset; if (offset < nextOffset || token_policy == empty_token_policy::keep) { token.clear(); token.append(cstr + offset, cstr + nextOffset); - functor(token); + co_yield token; } offset = nextOffset + 1; } } -template -void split_on_any(const String& str, const Delim& delimiters, const Func& functor, empty_token_policy token_policy = empty_token_policy::discard) noexcept +template +cppcoro::generator split_on_any(const String& str, tl::string_view sep, empty_token_policy token_policy = empty_token_policy::discard) noexcept { - const size_t k_delims_size = delimiters.size(); - if (k_delims_size == 1) - return split_on_any(str, delimiters[0], functor, token_policy); - - if (k_delims_size == 0) + const size_t sep_size = sep.size(); + if (sep_size == 1) { - TL_PLAIN_FAIL("'delimiters' cannot be empty"); - return; + for (auto const& s: split_on_any(str, sep[0], token_policy)) + co_yield s; + co_return; + } + + if (sep_size == 0) + { + TL_PLAIN_FAIL("'sep' cannot be empty"); + co_return; } String token; const char* cstr = str.data(); - const char* delimsStr = delimiters.data(); + const char* sepStr = sep.data(); size_t offset = 0; const size_t endOffset = str.size(); while (offset <= endOffset) @@ -191,7 +153,7 @@ void split_on_any(const String& str, const Delim& delimiters, const Func& functo size_t nextOffset = offset; for (; nextOffset < endOffset; ++nextOffset) { - if (::memchr(delimsStr, cstr[nextOffset], k_delims_size) != nullptr) + if (::memchr(sepStr, cstr[nextOffset], sep_size) != nullptr) break; } @@ -199,47 +161,47 @@ void split_on_any(const String& str, const Delim& delimiters, const Func& functo { token.clear(); token.append(cstr + offset, cstr + nextOffset); - functor(token); + co_yield token; } offset = nextOffset + 1; } } -template -void split_on_all(const String& str, const Delim& sep, const Func& functor, empty_token_policy token_policy = empty_token_policy::discard) noexcept +template +cppcoro::generator split_on_all(const String& str, tl::string_view sep, empty_token_policy token_policy = empty_token_policy::discard) noexcept { - String separator(sep); - if (separator.empty()) + if (sep.empty()) { - TL_PLAIN_FAIL("'delimiters' cannot be empty"); - return; + TL_PLAIN_FAIL("'sep' cannot be empty"); + co_return; } size_t p0 = 0; size_t p1 = 0; + const String empty_token; String token; - while ((p1 = str.find(separator, p0)) != String::npos) + while ((p1 = str.find(sep, p0)) != String::npos) { if (p1 > p0) { token.clear(); token.append(str, p0, p1 - p0); - functor(token); + co_yield token; } else if (token_policy == empty_token_policy::keep) - functor(String{}); + co_yield empty_token; - p0 = p1 + separator.size(); + p0 = p1 + sep.size(); } if (str.size() > p0) { token.clear(); token.append(str, p0, String::npos); - functor(token); + co_yield token; } else if (token_policy == empty_token_policy::keep) - functor(String{}); + co_yield empty_token; } template @@ -541,13 +503,13 @@ size_t replace_first(const string& text, const string& target, const string& rep namespace detail { template -String join_worker(const Container& container, const String& delimiter) noexcept +String join_worker(const Container& container, const String& sep) noexcept { String dst; //reserve some space based on some heuristics: - // We'll have size - 1 delimiters + // We'll have size - 1 seps // And an average string size of 2 chars. The small choice is made to help with the small string case only where the allocator overhead is significant. - dst.reserve((delimiter.size() + 2) * container.size()); + dst.reserve((sep.size() + 2) * container.size()); auto end = std::end(container); auto it = std::begin(container); @@ -558,19 +520,19 @@ String join_worker(const Container& container, const String& delimiter) noexcept if (it == end) break; - dst += delimiter; + dst += sep; } return dst; } template -string join_worker(const Container& container, const string& delimiter) noexcept +string join_worker(const Container& container, const string& sep) noexcept { eastl::string buffer; //reserve some space based on some heuristics: - // We'll have size - 1 delimiters + // We'll have size - 1 seps // And an average string size of 2 chars. The small choice is made to help with the small string case only where the allocator overhead is significant. - buffer.reserve((delimiter.size() + 2) * container.size()); + buffer.reserve((sep.size() + 2) * container.size()); auto end = std::end(container); auto it = std::begin(container); @@ -582,41 +544,41 @@ string join_worker(const Container& container, const string& delimiter) noexcept if (it == end) break; - buffer.append(delimiter.data(), delimiter.size()); + buffer.append(sep.data(), sep.size()); } return string(buffer); } -//joiner used when the Delim and String are of the same type -template +//joiner used when the Sep and String are of the same type +template std::enable_if_t< std::is_same_v< std::remove_const_t>, - std::remove_const_t>>, - String> join(const Container& container, const Delim& delimiter) noexcept + std::remove_const_t>>, + String> join(const Container& container, const Sep& sep) noexcept { - return join_worker(container, delimiter); + return join_worker(container, sep); } -//joiner used when the Delim and String are different types -template +//joiner used when the Sep and String are different types +template std::enable_if_t< !std::is_same_v< std::remove_const_t>, - std::remove_const_t>>, - String> join(const Container& container, const Delim& delimiter) noexcept + std::remove_const_t>>, + String> join(const Container& container, const Sep& sep) noexcept { - return join_worker(container, String(delimiter)); + return join_worker(container, String(sep)); } } //namespace detail -template -String join(const Container& container, const Delim& delimiter) noexcept +template +String join(const Container& container, const Sep& sep) noexcept { if (container.empty()) return String(); - return detail::join(container, delimiter); + return detail::join(container, sep); } } } diff --git a/include/tl/ptr.h b/include/tl/ptr.h index 237f8f3..9045692 100644 --- a/include/tl/ptr.h +++ b/include/tl/ptr.h @@ -113,7 +113,7 @@ extern thread_local int32_t s_ptr_checking_disabled; #define CB_LENT_DEC_REF() \ if (m_cb) [[likely]] \ { \ - bool is_dangling = m_cb->unique_count.load(tl::memory_order_acquire) == 0; \ + const bool is_dangling = m_cb->unique_count.load(tl::memory_order_acquire) == 0; \ if (is_dangling && is_ptr_checking_enabled()) [[unlikely]] \ TL_FAIL("lent: {} dangling lent ptrs detected", m_cb->lent_count.load(tl::memory_order_acquire)); \ if (m_cb->lent_count.fetch_sub(1, tl::memory_order_acquire) == 1) /* last one? cleanup */ \ @@ -144,7 +144,7 @@ extern thread_local int32_t s_ptr_checking_disabled; #define CB_UNIQUE_DEC_REF() \ if (m_cb && m_cb->unique_count.fetch_sub(1, tl::memory_order_acquire) == 1) [[likely]] \ { \ - int32_t count = m_cb->lent_count.load(tl::memory_order_acquire); \ + const int32_t count = m_cb->lent_count.load(tl::memory_order_acquire); \ if (count == 0) [[likely]] \ delete m_cb; \ else if (is_ptr_checking_enabled()) [[likely]] \