diff options
author | innokentii <innokentii@yandex-team.com> | 2022-09-16 17:12:05 +0300 |
---|---|---|
committer | innokentii <innokentii@yandex-team.com> | 2022-09-16 17:12:05 +0300 |
commit | 2df8226558ba61b949a051ef03f59e2a60443cd4 (patch) | |
tree | 66c716bc8ce6f5ce13902a153581da32ffc7f792 /library/cpp | |
parent | 0543d8ded75208f6c8de016998adb0d8b9b7530d (diff) | |
download | ydb-2df8226558ba61b949a051ef03f59e2a60443cd4.tar.gz |
Add TContiguousData
extract TChunk as TContiguousData
Diffstat (limited to 'library/cpp')
-rw-r--r-- | library/cpp/actors/util/contiguous_data.h | 671 | ||||
-rw-r--r-- | library/cpp/actors/util/contiguous_data_ut.cpp | 52 | ||||
-rw-r--r-- | library/cpp/actors/util/rope.h | 556 | ||||
-rw-r--r-- | library/cpp/actors/util/rope_cont_embedded_list.h | 2 | ||||
-rw-r--r-- | library/cpp/actors/util/rope_ut.cpp | 63 | ||||
-rw-r--r-- | library/cpp/actors/util/shared_data_native_rope_backend_ut.cpp | 231 | ||||
-rw-r--r-- | library/cpp/actors/util/shared_data_rope_backend.h | 10 | ||||
-rw-r--r-- | library/cpp/actors/util/ut_helpers.h | 12 |
8 files changed, 1109 insertions, 488 deletions
diff --git a/library/cpp/actors/util/contiguous_data.h b/library/cpp/actors/util/contiguous_data.h new file mode 100644 index 00000000000..ae4c913c20d --- /dev/null +++ b/library/cpp/actors/util/contiguous_data.h @@ -0,0 +1,671 @@ +#pragma once + +#include <util/generic/ptr.h> +#include <util/generic/string.h> +#include <util/generic/hash_set.h> +#include <util/generic/scope.h> +#include <util/stream/str.h> +#include <util/system/sanitizers.h> +#include <util/system/valgrind.h> +#include <util/generic/array_ref.h> + +#include "shared_data.h" + +namespace NContiguousDataDetails { + template<typename TContainer> + struct TContainerTraits { + static char* UnsafeGetDataMut(const TContainer& backend) { + return const_cast<char*>(backend.data()); + } + }; +} // NContiguousDataDetails + +class TContiguousSpan +{ +private: + const char *Data = nullptr; + size_t Size = 0; + +public: + TContiguousSpan() = default; + + TContiguousSpan(const char *data, size_t size) + : Data(data) + , Size(size) + {} + + TContiguousSpan(const TString& str) + : Data(str.data()) + , Size(str.size()) + {} + + TContiguousSpan(const TStringBuf& str) + : Data(str.data()) + , Size(str.size()) + {} + + TContiguousSpan(const TArrayRef<char>& ref) + : Data(ref.data()) + , Size(ref.size()) + {} + + TContiguousSpan(const TArrayRef<const char>& ref) + : Data(ref.data()) + , Size(ref.size()) + {} + + TContiguousSpan(const NActors::TSharedData& data) + : Data(data.data()) + , Size(data.size()) + {} + + const char *data() const noexcept { + return Data; + } + + size_t size() const noexcept { + return Size; + } + + const char *GetData() const noexcept { + return Data; + } + + size_t GetSize() const noexcept { + return Size; + } + + TContiguousSpan SubSpan(size_t pos, size_t n) const noexcept { + pos = Min(pos, size()); + n = Min(n, size() - pos); + return TContiguousSpan(data() + pos, n); + } + + friend bool operator==(const TContiguousSpan& x, const TContiguousSpan& y) { return Compare(x, y) == 0; } + friend bool operator!=(const TContiguousSpan& x, const TContiguousSpan& y) { return Compare(x, y) != 0; } + friend bool operator< (const TContiguousSpan& x, const TContiguousSpan& y) { return Compare(x, y) < 0; } + friend bool operator<=(const TContiguousSpan& x, const TContiguousSpan& y) { return Compare(x, y) <= 0; } + friend bool operator> (const TContiguousSpan& x, const TContiguousSpan& y) { return Compare(x, y) > 0; } + friend bool operator>=(const TContiguousSpan& x, const TContiguousSpan& y) { return Compare(x, y) >= 0; } + +private: + static int Compare(const TContiguousSpan& x, const TContiguousSpan& y) { + if(int res = std::memcmp(x.data(), y.data(), std::min(x.size(), y.size())); res) { + return res; + } + return x.size() - y.size(); + } +}; + +template < + class TLeft, + class TRight, + typename std::enable_if<std::is_convertible<TLeft,TContiguousSpan>::value>::type* = nullptr, + typename std::enable_if<std::is_convertible<TRight,TContiguousSpan>::value>::type* = nullptr + > +bool operator==(const TLeft& lhs, const TRight& rhs) { + return TContiguousSpan(lhs) == TContiguousSpan(rhs); +} + +template < + class TLeft, + class TRight, + typename std::enable_if<std::is_convertible<TLeft,TContiguousSpan>::value>::type* = nullptr, + typename std::enable_if<std::is_convertible<TRight,TContiguousSpan>::value>::type* = nullptr + > +bool operator!=(const TLeft& lhs, const TRight& rhs) { + return TContiguousSpan(lhs) != TContiguousSpan(rhs); +} + +template < + class TLeft, + class TRight, + typename std::enable_if<std::is_convertible<TLeft,TContiguousSpan>::value>::type* = nullptr, + typename std::enable_if<std::is_convertible<TRight,TContiguousSpan>::value>::type* = nullptr + > +bool operator<(const TLeft& lhs, const TRight& rhs) { + return TContiguousSpan(lhs) < TContiguousSpan(rhs); +} + +template < + class TLeft, + class TRight, + typename std::enable_if<std::is_convertible<TLeft,TContiguousSpan>::value>::type* = nullptr, + typename std::enable_if<std::is_convertible<TRight,TContiguousSpan>::value>::type* = nullptr + > +bool operator<=(const TLeft& lhs, const TRight& rhs) { + return TContiguousSpan(lhs) <= TContiguousSpan(rhs); +} + +template < + class TLeft, + class TRight, + typename std::enable_if<std::is_convertible<TLeft,TContiguousSpan>::value>::type* = nullptr, + typename std::enable_if<std::is_convertible<TRight,TContiguousSpan>::value>::type* = nullptr + > +bool operator>(const TLeft& lhs, const TRight& rhs) { + return TContiguousSpan(lhs) > TContiguousSpan(rhs); +} + +template < + class TLeft, + class TRight, + typename std::enable_if<std::is_convertible<TLeft,TContiguousSpan>::value>::type* = nullptr, + typename std::enable_if<std::is_convertible<TRight,TContiguousSpan>::value>::type* = nullptr + > +bool operator>=(const TLeft& lhs, const TRight& rhs) { + return TContiguousSpan(lhs) >= TContiguousSpan(rhs); +} + + +class TMutableContiguousSpan +{ +private: + char *Data = nullptr; + size_t Size = 0; + +public: + TMutableContiguousSpan() = default; + + TMutableContiguousSpan(char *data, size_t size) + : Data(data) + , Size(size) + {} + + char *data() noexcept { + return Data; + } + + char *GetData() noexcept { + return Data; + } + + TMutableContiguousSpan SubSpan(size_t pos, size_t n) noexcept { + pos = Min(pos, size()); + n = Min(n, size() - pos); + return TMutableContiguousSpan(data() + pos, n); + } + + const char *data() const noexcept { + return Data; + } + + size_t size() const noexcept { + return Size; + } + + const char *GetData() const noexcept { + return Data; + } + + size_t GetSize() const noexcept { + return Size; + } + + TContiguousSpan SubSpan(size_t pos, size_t n) const noexcept { + pos = Min(pos, size()); + n = Min(n, size() - pos); + return TContiguousSpan(data() + pos, n); + } + + operator TContiguousSpan() const noexcept { + return TContiguousSpan(Data, Size); + } +}; + +struct IContiguousChunk : TThrRefBase { + using TPtr = TIntrusivePtr<IContiguousChunk>; + + virtual ~IContiguousChunk() = default; + + /** + * Should give immutable access to data + */ + virtual TContiguousSpan GetData() const = 0; + + /** + * Should give mutable access to underlying data + * If data is shared - data should be copied + * E.g. for TString str.Detach() should be used + * Possibly invalidates previous *GetData*() calls + */ + virtual TMutableContiguousSpan GetDataMut() = 0; + + /** + * Should give mutable access to undelying data as fast as possible + * Even if data is shared this property should be ignored + * E.g. in TString const_cast<char *>(str.data()) should be used + * Possibly invalidates previous *GetData*() calls + */ + virtual TMutableContiguousSpan UnsafeGetDataMut() { + return GetDataMut(); + } + + virtual size_t GetCapacity() const = 0; +}; + +class TRope; +class TRopeArena; + +class TContiguousData { + friend class TRope; + friend class TRopeArena; + class TBackend { + enum class EType : uintptr_t { + STRING, + SHARED_DATA, + ROPE_CHUNK_BACKEND, + }; + + struct TBackendHolder { + uintptr_t Data[2]; + operator bool() const noexcept { + return Data[0] || Data[1]; + } + }; + + constexpr static TBackendHolder Empty = {0, 0}; + +#ifndef TSTRING_IS_STD_STRING + static_assert(sizeof(TBackendHolder) >= sizeof(TString)); +#endif + static_assert(sizeof(TBackendHolder) >= sizeof(NActors::TSharedData)); + + TBackendHolder Owner = TBackend::Empty; // lower bits contain type of the owner + + public: + TBackend() = default; + + TBackend(const TBackend& other) + : Owner(Clone(other.Owner)) + {} + + TBackend(TBackend&& other) + : Owner(std::exchange(other.Owner, TBackend::Empty)) + {} + + TBackend(TString s) + : Owner(Construct<TString>(EType::STRING, std::move(s))) + {} + + TBackend(NActors::TSharedData s) + : Owner(Construct<NActors::TSharedData>(EType::SHARED_DATA, std::move(s))) + {} + + TBackend(IContiguousChunk::TPtr backend) + : Owner(Construct<IContiguousChunk::TPtr>(EType::ROPE_CHUNK_BACKEND, std::move(backend))) + {} + + ~TBackend() { + if (Owner) { + Destroy(Owner); + } + } + + TBackend& operator =(const TBackend& other) { + if (Y_UNLIKELY(this == &other)) { + return *this; + } + + if (Owner) { + Destroy(Owner); + } + Owner = Clone(other.Owner); + return *this; + } + + TBackend& operator =(TBackend&& other) { + if (Y_UNLIKELY(this == &other)) { + return *this; + } + + if (Owner) { + Destroy(Owner); + } + Owner = std::exchange(other.Owner, TBackend::Empty); + return *this; + } + + bool operator ==(const TBackend& other) const { + return Owner == other.Owner; + } + + const void *UniqueId() const { + return reinterpret_cast<const void*>(Owner.Data[0]); + } + + TContiguousSpan GetData() const { + return Visit(Owner, [](EType, auto& value) -> TContiguousSpan { + using T = std::decay_t<decltype(value)>; + if constexpr (std::is_same_v<T, TString>) { + return {value.data(), value.size()}; + } else if constexpr (std::is_same_v<T, NActors::TSharedData>) { + return {value.data(), value.size()}; + } else if constexpr (std::is_same_v<T, IContiguousChunk::TPtr>) { + return value->GetData(); + } else { + return {}; + } + }); + } + + TMutableContiguousSpan GetDataMut() { + return Visit(Owner, [](EType, auto& value) -> TMutableContiguousSpan { + using T = std::decay_t<decltype(value)>; + if constexpr (std::is_same_v<T, TString>) { + return {value.Detach(), value.size()}; + } else if constexpr (std::is_same_v<T, NActors::TSharedData>) { + if(value.IsShared()) { + value = NActors::TSharedData::Copy(value.data(), value.size()); + } + return {value.mutable_data(), value.size()}; + } else if constexpr (std::is_same_v<T, IContiguousChunk::TPtr>) { + return value->GetDataMut(); + } else { + return {}; + } + }); + } + + TMutableContiguousSpan UnsafeGetDataMut() const { + return Visit(Owner, [](EType, auto& value) -> TMutableContiguousSpan { + using T = std::decay_t<decltype(value)>; + if constexpr (std::is_same_v<T, TString>) { + return {const_cast<char*>(value.data()), value.size()}; + } else if constexpr (std::is_same_v<T, NActors::TSharedData>) { + return {const_cast<char*>(value.data()), value.size()}; + } else if constexpr (std::is_same_v<T, IContiguousChunk::TPtr>) { + return value->UnsafeGetDataMut(); + } else { + return {}; + } + }); + } + + size_t GetCapacity() const { + return Visit(Owner, [](EType, auto& value) { + using T = std::decay_t<decltype(value)>; + if constexpr (std::is_same_v<T, TString>) { + return value.capacity(); + } else if constexpr (std::is_same_v<T, NActors::TSharedData>) { + return value.size(); // There is no capacity + } else if constexpr (std::is_same_v<T, IContiguousChunk::TPtr>) { + return value->GetCapacity(); + } else { + Y_FAIL(); + } + }); + } + + template <class TType> + bool ContainsNativeType() const { + return Visit(Owner, [](EType, auto& value) { + using T = std::decay_t<decltype(value)>; + return std::is_same_v<T, TType>; + }); + } + + template <class TResult> + TResult GetRaw() const { + return Visit(Owner, [](EType, auto& value) { + using T = std::decay_t<decltype(value)>; + if constexpr (std::is_same_v<T, TResult>) { + return value; + } else { + Y_FAIL(); + return TResult{}; // unreachable + } + }); + } + + explicit operator bool() const { + return Owner; + } + + private: + static constexpr uintptr_t TypeMask = (1 << 3) - 1; + static constexpr uintptr_t ValueMask = ~TypeMask; + + template<typename T> + struct TObjectHolder { + struct TWrappedObject : TThrRefBase { + T Value; + TWrappedObject(T&& value) + : Value(std::move(value)) + {} + }; + TIntrusivePtr<TWrappedObject> Object; + + TObjectHolder(T&& object) + : Object(MakeIntrusive<TWrappedObject>(std::move(object))) + {} + }; + + template<typename TObject> + static TBackendHolder Construct(EType type, TObject object) { + if constexpr (sizeof(TObject) <= sizeof(TBackendHolder)) { + TBackendHolder res = TBackend::Empty; + new(&res) TObject(std::move(object)); + Y_VERIFY_DEBUG((res.Data[0] & ValueMask) == res.Data[0]); + res.Data[0] = res.Data[0] | static_cast<uintptr_t>(type); + return res; + } else { + return Construct<TObjectHolder<TObject>>(type, TObjectHolder<TObject>(std::move(object))); + } + } + + template<typename TOwner, typename TCallback, bool IsConst = std::is_const_v<TOwner>> + static std::invoke_result_t<TCallback, EType, std::conditional_t<IsConst, const TString&, TString&>> VisitRaw(TOwner& origValue, TCallback&& callback) { + Y_VERIFY_DEBUG(origValue); + const EType type = static_cast<EType>(origValue.Data[0] & TypeMask); + TBackendHolder value(origValue); + value.Data[0] = value.Data[0] & ValueMask; + // bring object type back + Y_SCOPE_EXIT(&value, &origValue, type){ + if constexpr(!IsConst) { + value.Data[0] = value.Data[0] | static_cast<uintptr_t>(type); + origValue = value; + } else { + Y_UNUSED(value); + Y_UNUSED(origValue); + Y_UNUSED(type); + } + }; + auto caller = [&](auto& value) { return std::invoke(std::forward<TCallback>(callback), type, value); }; + auto wrapper = [&](auto& value) { + using T = std::decay_t<decltype(value)>; + if constexpr (sizeof(T) <= sizeof(TBackendHolder)) { + return caller(value); + } else { + return caller(reinterpret_cast<std::conditional_t<IsConst, const TObjectHolder<T>&, TObjectHolder<T>&>>(value)); + } + }; + switch (type) { + case EType::STRING: return wrapper(reinterpret_cast<std::conditional_t<IsConst, const TString&, TString&>>(value)); + case EType::SHARED_DATA: return wrapper(reinterpret_cast<std::conditional_t<IsConst, const NActors::TSharedData&, NActors::TSharedData&>>(value)); + case EType::ROPE_CHUNK_BACKEND: return wrapper(reinterpret_cast<std::conditional_t<IsConst, const IContiguousChunk::TPtr&, IContiguousChunk::TPtr&>>(value)); + } + Y_FAIL("Unexpected type# %" PRIu64, static_cast<ui64>(type)); + } + + template<typename TOwner, typename TCallback, bool IsConst = std::is_const_v<TOwner>> + static std::invoke_result_t<TCallback, EType, std::conditional_t<IsConst, const TString&, TString&>> Visit(TOwner& value, TCallback&& callback) { + return VisitRaw(value, [&](EType type, auto& value) { + return std::invoke(std::forward<TCallback>(callback), type, Unwrap(value)); + }); + } + + template<typename T> static T& Unwrap(T& object) { return object; } + template<typename T> static T& Unwrap(TObjectHolder<T>& holder) { return holder.Object->Value; } + template<typename T> static const T& Unwrap(const TObjectHolder<T>& holder) { return holder.Object->Value; } + + template<typename TOwner> + static TBackendHolder Clone(TOwner& value) { + return VisitRaw(value, [](EType type, auto& value) { return Construct(type, value); }); + } + + template<typename TOwner> + static void Destroy(TOwner& value) { + VisitRaw(value, [](EType, auto& value) { CallDtor(value); }); + } + + template<typename T> + static void CallDtor(T& value) { + value.~T(); + } + }; + + TBackend Backend; // who actually holds the data + const char *Begin; // data start + const char *End; // data end + +public: + static constexpr struct TSlice {} Slice{}; + + TContiguousData() + : Begin(nullptr) + , End(nullptr) + {} + + template<typename T> + TContiguousData(T&& backend, const TContiguousSpan& data) + : Backend(std::forward<T>(backend)) + , Begin(data.data()) + , End(Begin + data.size()) + { + Y_VERIFY_DEBUG(Begin != End); + } + + explicit TContiguousData(TString s) + : Backend(std::move(s)) + { + auto span = Backend.GetData(); + Begin = span.data(); + End = Begin + span.size(); + } + + explicit TContiguousData(NActors::TSharedData s) + : Backend(std::move(s)) + { + auto span = Backend.GetData(); + Begin = span.data(); + End = Begin + span.size(); + } + + TContiguousData(IContiguousChunk::TPtr backend) + : TContiguousData(backend, backend->GetData()) + {} + + TContiguousData(TSlice, const char *data, size_t size, const TContiguousData& from) + : TContiguousData(from.Backend, {data, size}) + {} + + TContiguousData(TSlice, const char *begin, const char *end, const TContiguousData& from) + : TContiguousData(Slice, begin, end - begin, from) + {} + + TContiguousData(const TContiguousData& other) + : Backend(other.Backend) + , Begin(other.Begin) + , End(other.End) + {} + + TContiguousData(TContiguousData&& other) + : Backend(std::move(other.Backend)) + , Begin(other.Begin) + , End(other.End) + {} + + TContiguousData& operator =(const TContiguousData&) = default; + TContiguousData& operator =(TContiguousData&&) = default; + + template <class TType> + bool ContainsNativeType() const { + return Backend.ContainsNativeType<TType>(); + } + + template <class TResult> + TResult GetRaw() const { + return Backend.GetRaw<TResult>(); + } + + bool ReferencesWholeContainer() const { + return Backend.GetData().size() == GetSize(); + } + + size_t GetSize() const { + return End - Begin; + } + + size_t GetCapacity() const { + return Backend.GetCapacity(); + } + + const char* GetData() const { + return Begin; + } + + char* GetDataMut() { + const char* oldBegin = Backend.GetData().data(); + ptrdiff_t offset = Begin - oldBegin; + size_t size = GetSize(); + char* newBegin = Backend.GetDataMut().data(); + Begin = newBegin + offset; + End = Begin + size; + return newBegin + offset; + } + + char* UnsafeGetDataMut() { + const char* oldBegin = Backend.GetData().data(); + ptrdiff_t offset = Begin - oldBegin; + size_t size = GetSize(); + char* newBegin = Backend.UnsafeGetDataMut().data(); + Begin = newBegin + offset; + End = Begin + size; + return newBegin + offset; + } + + template <class TResult> + TResult ExtractUnderlyingContainerOrCopy() const { + if (ContainsNativeType<TResult>() && ReferencesWholeContainer()) { + return GetRaw<TResult>(); + } + + TResult res = TResult::Uninitialized(GetSize()); + char* data = NContiguousDataDetails::TContainerTraits<TResult>::UnsafeGetDataMut(res); + std::memcpy(data, Begin, End - Begin); + return res; + } + + TContiguousSpan GetContiguousSpan() const { + return {GetData(), GetSize()}; + } + + TMutableContiguousSpan GetContiguousSpanMut() { + return {GetDataMut(), GetSize()}; + } + + TMutableContiguousSpan UnsafeGetContiguousSpanMut() { + return {UnsafeGetDataMut(), GetSize()}; + } + + size_t size() const { + return GetSize(); + } + + size_t capacity() const { + return GetCapacity(); + } + + const char* data() const { + return GetData(); + } + + operator TContiguousSpan() const noexcept { + return TContiguousSpan(GetData(), GetSize()); + } + + explicit operator TMutableContiguousSpan() noexcept { + return TMutableContiguousSpan(GetDataMut(), GetSize()); + } +}; diff --git a/library/cpp/actors/util/contiguous_data_ut.cpp b/library/cpp/actors/util/contiguous_data_ut.cpp new file mode 100644 index 00000000000..b9361b3f394 --- /dev/null +++ b/library/cpp/actors/util/contiguous_data_ut.cpp @@ -0,0 +1,52 @@ +#include "contiguous_data.h" +#include "ut_helpers.h" +#include "rope.h" + +#include <library/cpp/testing/unittest/registar.h> +#include <util/random/random.h> + +Y_UNIT_TEST_SUITE(TContiguousData) { + Y_UNIT_TEST(TypeSize) { + UNIT_ASSERT_EQUAL(sizeof(TContiguousData), 4 * sizeof(uintptr_t)); + } + + Y_UNIT_TEST(CrossCompare) { + TString str = "some very long string"; + const TString constStr(str); + TStringBuf strbuf = str; + const TStringBuf constStrbuf = str; + TContiguousSpan span(str); + const TContiguousSpan constSpan(str); + TMutableContiguousSpan mutableSpan(const_cast<char*>(str.data()), str.size()); + const TMutableContiguousSpan constMutableSpan(const_cast<char*>(str.data()), str.size()); + TContiguousData data(str); + const TContiguousData constData(str); + TArrayRef<char> arrRef(const_cast<char*>(str.data()), str.size()); + const TArrayRef<char> constArrRef(const_cast<char*>(str.data()), str.size()); + TArrayRef<const char> arrConstRef(const_cast<char*>(str.data()), str.size()); + const TArrayRef<const char> constArrConstRef(const_cast<char*>(str.data()), str.size()); + NActors::TSharedData sharedData = NActors::TSharedData::Copy(str.data(), str.size()); + const NActors::TSharedData constSharedData(sharedData); + + Permutate( + [](auto& arg1, auto& arg2) { + UNIT_ASSERT(arg1 == arg2); + }, + str, + constStr, + strbuf, + constStrbuf, + span, + constSpan, + mutableSpan, + constMutableSpan, + data, + constData, + arrRef, + constArrRef, + arrConstRef, + constArrConstRef, + sharedData, + constSharedData); + } +} diff --git a/library/cpp/actors/util/rope.h b/library/cpp/actors/util/rope.h index 4e5336dc637..2a2e25f3a29 100644 --- a/library/cpp/actors/util/rope.h +++ b/library/cpp/actors/util/rope.h @@ -13,33 +13,9 @@ //#include "rope_cont_list.h" //#include "rope_cont_deque.h" -struct IRopeChunkBackend : TThrRefBase { - using TData = std::tuple<const char*, size_t>; - using TMutData = std::tuple<char*, size_t>; - virtual ~IRopeChunkBackend() = default; - /** - * Should give immutable access to data - */ - virtual TData GetData() const = 0; - /** - * Should give mutable access to underlying data - * If data is shared - data should be copied - * E.g. for TString str.Detach() should be used - */ - virtual TMutData GetDataMut() = 0; - /** - * Should give mutable access to undelying data as fast as possible - * Even if data is shared this property should be ignored - * E.g. in TString const_cast<char *>(str.data()) should be used - */ - virtual TMutData UnsafeGetDataMut() { - return GetDataMut(); - } - virtual size_t GetCapacity() const = 0; - using TPtr = TIntrusivePtr<IRopeChunkBackend>; -}; +#include "contiguous_data.h" -class TRopeAlignedBuffer : public IRopeChunkBackend { +class TRopeAlignedBuffer : public IContiguousChunk { static constexpr size_t Alignment = 16; static constexpr size_t MallocAlignment = sizeof(size_t); @@ -78,11 +54,11 @@ public: Y_UNUSED(ptr); } - TData GetData() const override { + TContiguousSpan GetData() const override { return {Data + Offset, Size}; } - TMutData GetDataMut() override { + TMutableContiguousSpan GetDataMut() override { return {Data + Offset, Size}; } @@ -117,12 +93,6 @@ namespace NRopeDetails { using TListIterator = typename TList::iterator; }; - template<typename TContainer> - struct TContainerTraits { - static char* UnsafeGetDataMut(const TContainer& backend) { - return const_cast<char*>(backend.data()); - } - }; } // NRopeDetails class TRopeArena; @@ -133,332 +103,7 @@ struct always_false : std::false_type {}; class TRope { friend class TRopeArena; - struct TChunk - { - class TBackend { - enum class EType : uintptr_t { - STRING, - ROPE_CHUNK_BACKEND, - }; - - uintptr_t Owner = 0; // lower bits contain type of the owner - - public: - TBackend() = default; - - TBackend(const TBackend& other) - : Owner(Clone(other.Owner)) - {} - - TBackend(TBackend&& other) - : Owner(std::exchange(other.Owner, 0)) - {} - - TBackend(TString s) - : Owner(Construct<TString>(EType::STRING, std::move(s))) - {} - - TBackend(IRopeChunkBackend::TPtr backend) - : Owner(Construct<IRopeChunkBackend::TPtr>(EType::ROPE_CHUNK_BACKEND, std::move(backend))) - {} - - ~TBackend() { - if (Owner) { - Destroy(Owner); - } - } - - TBackend& operator =(const TBackend& other) { - if (Owner) { - Destroy(Owner); - } - Owner = Clone(other.Owner); - return *this; - } - - TBackend& operator =(TBackend&& other) { - if (Owner) { - Destroy(Owner); - } - Owner = std::exchange(other.Owner, 0); - return *this; - } - - bool operator ==(const TBackend& other) const { - return Owner == other.Owner; - } - - const void *UniqueId() const { - return reinterpret_cast<const void*>(Owner); - } - - const IRopeChunkBackend::TData GetData() const { - return Visit(Owner, [](EType, auto& value) -> IRopeChunkBackend::TData { - using T = std::decay_t<decltype(value)>; - if constexpr (std::is_same_v<T, TString>) { - return {value.data(), value.size()}; - } else if constexpr (std::is_same_v<T, IRopeChunkBackend::TPtr>) { - return value->GetData(); - } else { - return {}; - } - }); - } - - const IRopeChunkBackend::TMutData GetDataMut() { - return Visit(Owner, [](EType, auto& value) -> IRopeChunkBackend::TMutData { - using T = std::decay_t<decltype(value)>; - if constexpr (std::is_same_v<T, TString>) { - return {value.Detach(), value.size()}; - } else if constexpr (std::is_same_v<T, IRopeChunkBackend::TPtr>) { - return value->GetDataMut(); - } else { - return {}; - } - }); - } - - const IRopeChunkBackend::TMutData UnsafeGetDataMut() const { - return Visit(Owner, [](EType, auto& value) -> IRopeChunkBackend::TMutData { - using T = std::decay_t<decltype(value)>; - if constexpr (std::is_same_v<T, TString>) { - return {const_cast<char*>(value.data()), value.size()}; - } else if constexpr (std::is_same_v<T, IRopeChunkBackend::TPtr>) { - return value->UnsafeGetDataMut(); - } else { - return {}; - } - }); - } - - size_t GetCapacity() const { - return Visit(Owner, [](EType, auto& value) { - using T = std::decay_t<decltype(value)>; - if constexpr (std::is_same_v<T, TString>) { - return value.capacity(); - } else if constexpr (std::is_same_v<T, IRopeChunkBackend::TPtr>) { - return value->GetCapacity(); - } else { - Y_FAIL(); - } - }); - } - - template <class TType> - bool ContainsNativeType() const { - return Visit(Owner, [](EType, auto& value) { - using T = std::decay_t<decltype(value)>; - return std::is_same_v<T, TType>; - }); - } - - template <class TResult> - TResult GetRaw() const { - return Visit(Owner, [](EType, auto& value) { - using T = std::decay_t<decltype(value)>; - if constexpr (std::is_same_v<T, TResult>) { - return value; - } else { - Y_FAIL(); - return TResult{}; // unreachable - } - }); - } - - explicit operator bool() const { - return Owner; - } - - private: - static constexpr uintptr_t TypeMask = (1 << 3) - 1; - static constexpr uintptr_t ValueMask = ~TypeMask; - - template<typename T> - struct TObjectHolder { - struct TWrappedObject : TThrRefBase { - T Value; - TWrappedObject(T&& value) - : Value(std::move(value)) - {} - }; - TIntrusivePtr<TWrappedObject> Object; - - TObjectHolder(T&& object) - : Object(MakeIntrusive<TWrappedObject>(std::move(object))) - {} - }; - - template<typename TObject> - static uintptr_t Construct(EType type, TObject object) { - if constexpr (sizeof(TObject) <= sizeof(uintptr_t)) { - uintptr_t res = 0; - new(&res) TObject(std::move(object)); - Y_VERIFY_DEBUG((res & ValueMask) == res); - return res | static_cast<uintptr_t>(type); - } else { - return Construct<TObjectHolder<TObject>>(type, TObjectHolder<TObject>(std::move(object))); - } - } - - template<typename TOwner, typename TCallback, bool IsConst = std::is_const_v<TOwner>> - static std::invoke_result_t<TCallback, EType, std::conditional_t<IsConst, const TString&, TString&>> VisitRaw(TOwner& origValue, TCallback&& callback) { - Y_VERIFY_DEBUG(origValue); - const EType type = static_cast<EType>(origValue & TypeMask); - uintptr_t value = origValue & ValueMask; - // bring object type back - Y_SCOPE_EXIT(&value, &origValue, type){ - if constexpr(!IsConst) { - origValue = value | static_cast<uintptr_t>(type); - } else { - Y_UNUSED(value); - Y_UNUSED(origValue); - Y_UNUSED(type); - } - }; - auto caller = [&](auto& value) { return std::invoke(std::forward<TCallback>(callback), type, value); }; - auto wrapper = [&](auto& value) { - using T = std::decay_t<decltype(value)>; - if constexpr (sizeof(T) <= sizeof(uintptr_t)) { - return caller(value); - } else { - return caller(reinterpret_cast<std::conditional_t<IsConst, const TObjectHolder<T>&, TObjectHolder<T>&>>(value)); - } - }; - switch (type) { - case EType::STRING: return wrapper(reinterpret_cast<std::conditional_t<IsConst, const TString&, TString&>>(value)); - case EType::ROPE_CHUNK_BACKEND: return wrapper(reinterpret_cast<std::conditional_t<IsConst, const IRopeChunkBackend::TPtr&, IRopeChunkBackend::TPtr&>>(value)); - } - Y_FAIL("Unexpected type# %" PRIu64, static_cast<ui64>(type)); - } - - template<typename TOwner, typename TCallback, bool IsConst = std::is_const_v<TOwner>> - static std::invoke_result_t<TCallback, EType, std::conditional_t<IsConst, const TString&, TString&>> Visit(TOwner& value, TCallback&& callback) { - return VisitRaw(value, [&](EType type, auto& value) { - return std::invoke(std::forward<TCallback>(callback), type, Unwrap(value)); - }); - } - - template<typename T> static T& Unwrap(T& object) { return object; } - template<typename T> static T& Unwrap(TObjectHolder<T>& holder) { return holder.Object->Value; } - template<typename T> static const T& Unwrap(const TObjectHolder<T>& holder) { return holder.Object->Value; } - - template<typename TOwner> - static uintptr_t Clone(TOwner& value) { - return VisitRaw(value, [](EType type, auto& value) { return Construct(type, value); }); - } - - template<typename TOwner> - static void Destroy(TOwner& value) { - VisitRaw(value, [](EType, auto& value) { CallDtor(value); }); - } - - template<typename T> - static void CallDtor(T& value) { - value.~T(); - } - }; - - TBackend Backend; // who actually holds the data - const char *Begin; // data start - const char *End; // data end - - static constexpr struct TSlice {} Slice{}; - - TChunk() - : Begin(nullptr) - , End(nullptr) - {} - - template<typename T> - TChunk(T&& backend, const IRopeChunkBackend::TData& data) - : Backend(std::forward<T>(backend)) - , Begin(std::get<0>(data)) - , End(Begin + std::get<1>(data)) - { - Y_VERIFY_DEBUG(Begin != End); - } - - TChunk(TString s) - : Backend(std::move(s)) - { - size_t size; - std::tie(Begin, size) = Backend.GetData(); - End = Begin + size; - } - - TChunk(IRopeChunkBackend::TPtr backend) - : TChunk(backend, backend->GetData()) - {} - - TChunk(TSlice, const char *data, size_t size, const TChunk& from) - : TChunk(from.Backend, {data, size}) - {} - - TChunk(TSlice, const char *begin, const char *end, const TChunk& from) - : TChunk(Slice, begin, end - begin, from) - {} - - explicit TChunk(const TChunk& other) - : Backend(other.Backend) - , Begin(other.Begin) - , End(other.End) - {} - - TChunk(TChunk&& other) - : Backend(std::move(other.Backend)) - , Begin(other.Begin) - , End(other.End) - {} - - TChunk& operator =(const TChunk&) = default; - TChunk& operator =(TChunk&&) = default; - - template <class TType> - bool ContainsNativeType() const { - return Backend.ContainsNativeType<TType>(); - } - - template <class TResult> - TResult GetRaw() const { - return Backend.GetRaw<TResult>(); - } - - bool OwnsWholeContainer() const { - auto [data, size] = Backend.GetData(); - return size == GetSize(); - } - - size_t GetSize() const { - return End - Begin; - } - - size_t GetCapacity() const { - return Backend.GetCapacity(); - } - - char* GetDataMut() { - const char* oldBegin = std::get<0>(Backend.GetData()); - ptrdiff_t offset = Begin - oldBegin; - size_t size = GetSize(); - char* newBegin = std::get<0>(Backend.GetDataMut()); - Begin = newBegin + offset; - End = Begin + size; - return newBegin + offset; - } - - char* UnsafeGetDataMut() { - const char* oldBegin = std::get<0>(Backend.GetData()); - ptrdiff_t offset = Begin - oldBegin; - size_t size = GetSize(); - char* newBegin = std::get<0>(Backend.UnsafeGetDataMut()); - Begin = newBegin + offset; - End = Begin + size; - return newBegin + offset; - } - - }; - - using TChunkList = NRopeDetails::TChunkList<TChunk>; + using TChunkList = NRopeDetails::TChunkList<TContiguousData>; private: // we use list here to store chain items as we have to keep valid iterators when erase/insert operations are invoked; @@ -666,13 +311,13 @@ private: return Iter; } - const TChunk& GetChunk() const { + const TContiguousData& GetChunk() const { CheckValid(); return *Iter; } template<bool Mut = !IsConst, std::enable_if_t<Mut, bool> = true> - TChunk& GetChunk() { + TContiguousData& GetChunk() { CheckValid(); return *Iter; } @@ -710,6 +355,14 @@ public: TRope() = default; TRope(const TRope& rope) = default; + TRope(const TContiguousData& data) { + Chain.PutToEnd(data); + } + + TRope(TContiguousData&& data) { + Chain.PutToEnd(std::move(data)); + } + TRope(TRope&& rope) : Chain(std::move(rope.Chain)) , Size(std::exchange(rope.Size, 0)) @@ -727,8 +380,13 @@ public: } } - TRope(IRopeChunkBackend::TPtr item) { - std::tie(std::ignore, Size) = item->GetData(); + explicit TRope(NActors::TSharedData s) { + Size = s.size(); + Chain.PutToEnd(std::move(s)); + } + + TRope(IContiguousChunk::TPtr item) { + Size = item->GetData().size(); Chain.PutToEnd(std::move(item)); } @@ -742,13 +400,13 @@ public: while (begin.Iter != end.Iter) { const size_t size = begin.ContiguousSize(); - Chain.PutToEnd(TChunk::Slice, begin.ContiguousData(), size, begin.GetChunk()); + Chain.PutToEnd(TContiguousData::Slice, begin.ContiguousData(), size, begin.GetChunk()); begin.AdvanceToNextContiguousBlock(); Size += size; } if (begin != end && end.PointsToChunkMiddle()) { - Chain.PutToEnd(TChunk::Slice, begin.Ptr, end.Ptr, begin.GetChunk()); + Chain.PutToEnd(TContiguousData::Slice, begin.Ptr, end.Ptr, begin.GetChunk()); Size += end.Ptr - begin.Ptr; } } @@ -869,7 +527,7 @@ public: return; } } - dest->Chain.PutToEnd(TChunk::Slice, first->Begin, first->Begin + num, *first); + dest->Chain.PutToEnd(TContiguousData::Slice, first->Begin, first->Begin + num, *first); first->Begin += num; } } @@ -887,14 +545,14 @@ public: // check if we have to split the block if (pos.PointsToChunkMiddle()) { - pos.Iter = Chain.InsertBefore(pos.Iter, TChunk::Slice, pos->Begin, pos.Ptr, pos.GetChunk()); + pos.Iter = Chain.InsertBefore(pos.Iter, TContiguousData::Slice, pos->Begin, pos.Ptr, pos.GetChunk()); ++pos.Iter; pos->Begin = pos.Ptr; } // perform glueing if possible - TChunk *ropeLeft = &rope.Chain.GetFirstChunk(); - TChunk *ropeRight = &rope.Chain.GetLastChunk(); + TContiguousData *ropeLeft = &rope.Chain.GetFirstChunk(); + TContiguousData *ropeRight = &rope.Chain.GetLastChunk(); bool gluedLeft = false, gluedRight = false; if (pos.Iter != Chain.begin()) { // glue left part whenever possible // obtain iterator to previous chunk @@ -935,7 +593,7 @@ public: while (len) { Y_VERIFY_DEBUG(Chain); - TChunk& item = Chain.GetFirstChunk(); + TContiguousData& item = Chain.GetFirstChunk(); const size_t itemSize = item.GetSize(); if (len >= itemSize) { Chain.EraseFront(); @@ -955,7 +613,7 @@ public: while (len) { Y_VERIFY_DEBUG(Chain); - TChunk& item = Chain.GetLastChunk(); + TContiguousData& item = Chain.GetLastChunk(); const size_t itemSize = item.GetSize(); if (len >= itemSize) { Chain.EraseBack(); @@ -1012,7 +670,7 @@ public: return xIter.Valid() - yIter.Valid(); } - static int Compare(const TRope& x, const TString& y) { + static int Compare(const TRope& x, const TContiguousSpan& y) { TConstIterator xIter = x.Begin(); const char* yData = y.data(); size_t yOffset = 0; @@ -1027,7 +685,7 @@ public: return xIter.Valid() - (yOffset != y.size()); } - static int Compare(const TString& x, const TRope& y) { + static int Compare(const TContiguousSpan& x, const TRope& y) { return -Compare(y, x); } @@ -1046,94 +704,15 @@ public: TResult ExtractUnderlyingContainerOrCopy() const { if (IsContiguous() && GetSize() != 0) { const auto& chunk = Begin().GetChunk(); - if (chunk.ContainsNativeType<TResult>() && chunk.OwnsWholeContainer()) { - return chunk.GetRaw<TResult>(); - } + return chunk.ExtractUnderlyingContainerOrCopy<TResult>(); } TResult res = TResult::Uninitialized(GetSize()); - char* data = NRopeDetails::TContainerTraits<TResult>::UnsafeGetDataMut(res); + char* data = NContiguousDataDetails::TContainerTraits<TResult>::UnsafeGetDataMut(res); Begin().ExtractPlainDataAndAdvance(data, res.size()); return res; } - class TContiguousSpan; - class TMutableContiguousSpan { - friend class TContiguousSpan; - private: - char *Data; - size_t Size; - - public: - TMutableContiguousSpan(char *data, size_t size) - : Data(data) - , Size(size) - {} - - char *data() { - return Data; - } - - const char *data() const { - return Data; - } - - size_t size() const { - return Size; - } - - char *GetData() { - return Data; - } - - const char *GetData() const { - return Data; - } - - size_t GetSize() const { - return Size; - } - }; - - class TContiguousSpan { - private: - const char *Data; - size_t Size; - - public: - TContiguousSpan(const char *data, size_t size) - : Data(data) - , Size(size) - {} - - TContiguousSpan(const TMutableContiguousSpan& other) - : Data(other.Data) - , Size(other.Size) - {} - - TContiguousSpan& operator =(const TMutableContiguousSpan& other) { - Data = other.Data; - Size = other.Size; - return *this; - } - - const char *data() const { - return Data; - } - - size_t size() const { - return Size; - } - - const char *GetData() const { - return Data; - } - - size_t GetSize() const { - return Size; - } - }; - void clear() { Erase(Begin(), End()); } @@ -1170,7 +749,7 @@ public: return {nullptr, 0}; } Compact(); - return {Begin().ContiguousData(), Begin().ContiguousSize()}; + return Begin()->GetContiguousSpan(); } /** @@ -1182,7 +761,7 @@ public: return {nullptr, 0}; } Compact(); - return {Begin().ContiguousDataMut(), Begin().ContiguousSize()}; + return Begin()->GetContiguousSpanMut(); } /** @@ -1195,7 +774,7 @@ public: return {nullptr, 0}; } Compact(); - return {Begin().UnsafeContiguousDataMut(), Begin().ContiguousSize()}; + return Begin()->UnsafeGetContiguousSpanMut(); } TString DebugString() const { @@ -1203,13 +782,18 @@ public: s << "{Size# " << Size; for (const auto& chunk : Chain) { const char *data; - std::tie(data, std::ignore) = chunk.Backend.GetData(); + data = chunk.Backend.GetData().data(); s << " [" << chunk.Begin - data << ", " << chunk.End - data << ")@" << chunk.Backend.UniqueId(); } s << "}"; return s.Str(); } + explicit operator TContiguousData() { + Compact(); + return TContiguousData(Begin().GetChunk()); + } + friend bool operator==(const TRope& x, const TRope& y) { return Compare(x, y) == 0; } friend bool operator!=(const TRope& x, const TRope& y) { return Compare(x, y) != 0; } friend bool operator< (const TRope& x, const TRope& y) { return Compare(x, y) < 0; } @@ -1217,19 +801,35 @@ public: friend bool operator> (const TRope& x, const TRope& y) { return Compare(x, y) > 0; } friend bool operator>=(const TRope& x, const TRope& y) { return Compare(x, y) >= 0; } - friend bool operator==(const TRope& x, const TString& y) { return Compare(x, y) == 0; } - friend bool operator!=(const TRope& x, const TString& y) { return Compare(x, y) != 0; } - friend bool operator< (const TRope& x, const TString& y) { return Compare(x, y) < 0; } - friend bool operator<=(const TRope& x, const TString& y) { return Compare(x, y) <= 0; } - friend bool operator> (const TRope& x, const TString& y) { return Compare(x, y) > 0; } - friend bool operator>=(const TRope& x, const TString& y) { return Compare(x, y) >= 0; } + friend bool operator==(const TRope& x, const TContiguousSpan& y) { return Compare(x, y) == 0; } + friend bool operator!=(const TRope& x, const TContiguousSpan& y) { return Compare(x, y) != 0; } + friend bool operator< (const TRope& x, const TContiguousSpan& y) { return Compare(x, y) < 0; } + friend bool operator<=(const TRope& x, const TContiguousSpan& y) { return Compare(x, y) <= 0; } + friend bool operator> (const TRope& x, const TContiguousSpan& y) { return Compare(x, y) > 0; } + friend bool operator>=(const TRope& x, const TContiguousSpan& y) { return Compare(x, y) >= 0; } + + friend bool operator==(const TContiguousSpan& x, const TRope& y) { return Compare(x, y) == 0; } + friend bool operator!=(const TContiguousSpan& x, const TRope& y) { return Compare(x, y) != 0; } + friend bool operator< (const TContiguousSpan& x, const TRope& y) { return Compare(x, y) < 0; } + friend bool operator<=(const TContiguousSpan& x, const TRope& y) { return Compare(x, y) <= 0; } + friend bool operator> (const TContiguousSpan& x, const TRope& y) { return Compare(x, y) > 0; } + friend bool operator>=(const TContiguousSpan& x, const TRope& y) { return Compare(x, y) >= 0; } + + // FIXME(innokentii) temporary hack + friend bool operator==(const TRope& x, const TContiguousData& y) { return Compare(x, y.GetContiguousSpan()) == 0; } + friend bool operator!=(const TRope& x, const TContiguousData& y) { return Compare(x, y.GetContiguousSpan()) != 0; } + friend bool operator< (const TRope& x, const TContiguousData& y) { return Compare(x, y.GetContiguousSpan()) < 0; } + friend bool operator<=(const TRope& x, const TContiguousData& y) { return Compare(x, y.GetContiguousSpan()) <= 0; } + friend bool operator> (const TRope& x, const TContiguousData& y) { return Compare(x, y.GetContiguousSpan()) > 0; } + friend bool operator>=(const TRope& x, const TContiguousData& y) { return Compare(x, y.GetContiguousSpan()) >= 0; } + + friend bool operator==(const TContiguousData& x, const TRope& y) { return Compare(x.GetContiguousSpan(), y) == 0; } + friend bool operator!=(const TContiguousData& x, const TRope& y) { return Compare(x.GetContiguousSpan(), y) != 0; } + friend bool operator< (const TContiguousData& x, const TRope& y) { return Compare(x.GetContiguousSpan(), y) < 0; } + friend bool operator<=(const TContiguousData& x, const TRope& y) { return Compare(x.GetContiguousSpan(), y) <= 0; } + friend bool operator> (const TContiguousData& x, const TRope& y) { return Compare(x.GetContiguousSpan(), y) > 0; } + friend bool operator>=(const TContiguousData& x, const TRope& y) { return Compare(x.GetContiguousSpan(), y) >= 0; } - friend bool operator==(const TString& x, const TRope& y) { return Compare(x, y) == 0; } - friend bool operator!=(const TString& x, const TRope& y) { return Compare(x, y) != 0; } - friend bool operator< (const TString& x, const TRope& y) { return Compare(x, y) < 0; } - friend bool operator<=(const TString& x, const TRope& y) { return Compare(x, y) <= 0; } - friend bool operator> (const TString& x, const TRope& y) { return Compare(x, y) > 0; } - friend bool operator>=(const TString& x, const TRope& y) { return Compare(x, y) >= 0; } private: void Cut(TIterator begin, TIterator end, TRope *target) { @@ -1242,9 +842,9 @@ private: return; } - auto addBlock = [&](const TChunk& from, const char *begin, const char *end) { + auto addBlock = [&](const TContiguousData& from, const char *begin, const char *end) { if (target) { - target->Chain.PutToEnd(TChunk::Slice, begin, end, from); + target->Chain.PutToEnd(TContiguousData::Slice, begin, end, from); target->Size += end - begin; } Size -= end - begin; @@ -1257,7 +857,7 @@ private: const char *firstChunkBegin = begin.PointsToChunkMiddle() ? begin->Begin : nullptr; begin->Begin = end.Ptr; // this affects both begin and end iterator pointed values if (firstChunkBegin) { - Chain.InsertBefore(begin.Iter, TChunk::Slice, firstChunkBegin, begin.Ptr, begin.GetChunk()); + Chain.InsertBefore(begin.Iter, TContiguousData::Slice, firstChunkBegin, begin.Ptr, begin.GetChunk()); } } else { // check the first iterator -- if it starts not from the begin of the block, we have to adjust end of the @@ -1296,7 +896,7 @@ private: }; class TRopeArena { - using TAllocateCallback = std::function<TIntrusivePtr<IRopeChunkBackend>()>; + using TAllocateCallback = std::function<TIntrusivePtr<IContiguousChunk>()>; TAllocateCallback Allocator; TRope Arena; @@ -1339,7 +939,7 @@ public: return Size; } - void AccountChunk(const TRope::TChunk& chunk) { + void AccountChunk(const TContiguousData& chunk) { if (AccountedBuffers.insert(chunk.Backend.UniqueId()).second) { Size += chunk.GetCapacity(); } @@ -1472,7 +1072,7 @@ public: inline TRope TRope::CopySpaceOptimized(TRope&& origin, size_t worstRatioPer1k, TRopeArena& arena) { TRope res; - for (TChunk& chunk : origin.Chain) { + for (TContiguousData& chunk : origin.Chain) { size_t ratio = chunk.GetSize() * 1024 / chunk.GetCapacity(); if (ratio < 1024 - worstRatioPer1k) { res.Insert(res.End(), arena.CreateRope(chunk.Begin, chunk.GetSize())); @@ -1482,7 +1082,7 @@ inline TRope TRope::CopySpaceOptimized(TRope&& origin, size_t worstRatioPer1k, T } res.Size = origin.Size; origin = TRope(); - for (const TChunk& chunk : res.Chain) { + for (const TContiguousData& chunk : res.Chain) { arena.AccountChunk(chunk); } return res; diff --git a/library/cpp/actors/util/rope_cont_embedded_list.h b/library/cpp/actors/util/rope_cont_embedded_list.h index 94d50d5236d..80d59903106 100644 --- a/library/cpp/actors/util/rope_cont_embedded_list.h +++ b/library/cpp/actors/util/rope_cont_embedded_list.h @@ -48,7 +48,7 @@ class TChunkList { template<typename... TArgs> TItem *PrepareForUse(TArgs&&... args) { Y_VERIFY_DEBUG(!IsInUse()); - static_cast<TChunk&>(*this) = {std::forward<TArgs>(args)...}; + static_cast<TChunk&>(*this) = TChunk(std::forward<TArgs>(args)...); Next = Prev = this; Invalidate(); return this; diff --git a/library/cpp/actors/util/rope_ut.cpp b/library/cpp/actors/util/rope_ut.cpp index 1b2081ecedd..de7dd2c9eab 100644 --- a/library/cpp/actors/util/rope_ut.cpp +++ b/library/cpp/actors/util/rope_ut.cpp @@ -1,8 +1,9 @@ #include "rope.h" #include <library/cpp/testing/unittest/registar.h> #include <util/random/random.h> +#include "ut_helpers.h" -class TRopeStringBackend : public IRopeChunkBackend { +class TRopeStringBackend : public IContiguousChunk { TString Buffer; public: @@ -10,15 +11,15 @@ public: : Buffer(std::move(buffer)) {} - TData GetData() const override { + TContiguousSpan GetData() const override { return {Buffer.data(), Buffer.size()}; } - TMutData GetDataMut() override { + TMutableContiguousSpan GetDataMut() override { return {Buffer.Detach(), Buffer.size()}; } - TMutData UnsafeGetDataMut() override { + TMutableContiguousSpan UnsafeGetDataMut() override { return {const_cast<char*>(Buffer.data()), Buffer.size()}; } @@ -136,7 +137,61 @@ Y_UNIT_TEST_SUITE(TRope) { UNIT_ASSERT_EQUAL(pf.UnsafeGetContiguousSpanMut().data(), string.data()); UNIT_ASSERT(!string.IsDetached()); } + + Y_UNIT_TEST(ContiguousDataInterop) { + TString string = "Some long-long text needed for not sharing data and testing"; + TContiguousData data(string); + TRope rope(data); // check operator TRope + UNIT_ASSERT_EQUAL(rope.UnsafeGetContiguousSpanMut().data(), string.data()); + TContiguousData otherData(rope); + UNIT_ASSERT_EQUAL(otherData.UnsafeGetDataMut(), string.data()); + TString extractedBack = otherData.ExtractUnderlyingContainerOrCopy<TString>(); + UNIT_ASSERT_EQUAL(extractedBack.data(), string.data()); + } #endif + Y_UNIT_TEST(CrossCompare) { + TString str = "some very long string"; + const TString constStr(str); + TStringBuf strbuf = str; + const TStringBuf constStrbuf = str; + TContiguousSpan span(str); + const TContiguousSpan constSpan(str); + TMutableContiguousSpan mutableSpan(const_cast<char*>(str.data()), str.size()); + const TMutableContiguousSpan constMutableSpan(const_cast<char*>(str.data()), str.size()); + TContiguousData data(str); + const TContiguousData constData(str); + TArrayRef<char> arrRef(const_cast<char*>(str.data()), str.size()); + const TArrayRef<char> constArrRef(const_cast<char*>(str.data()), str.size()); + TArrayRef<const char> arrConstRef(const_cast<char*>(str.data()), str.size()); + const TArrayRef<const char> constArrConstRef(const_cast<char*>(str.data()), str.size()); + NActors::TSharedData sharedData = NActors::TSharedData::Copy(str.data(), str.size()); + const NActors::TSharedData constSharedData(sharedData); + TRope rope(str); + const TRope constRope(str); + + Permutate( + [](auto& arg1, auto& arg2) { + UNIT_ASSERT(arg1 == arg2); + }, + str, + constStr, + strbuf, + constStrbuf, + span, + constSpan, + mutableSpan, + constMutableSpan, + data, + constData, + arrRef, + constArrRef, + arrConstRef, + constArrConstRef, + sharedData, + constSharedData, + rope, + constRope); + } Y_UNIT_TEST(BasicRange) { TRope rope = CreateRope(Text, 10); diff --git a/library/cpp/actors/util/shared_data_native_rope_backend_ut.cpp b/library/cpp/actors/util/shared_data_native_rope_backend_ut.cpp new file mode 100644 index 00000000000..443fc149da1 --- /dev/null +++ b/library/cpp/actors/util/shared_data_native_rope_backend_ut.cpp @@ -0,0 +1,231 @@ +#include <library/cpp/actors/util/rope.h> +#include <library/cpp/testing/unittest/registar.h> +#include <util/random/random.h> + +#include "shared_data_rope_backend.h" + +namespace NActors { + + namespace { + + TRope CreateRope(TString s, size_t sliceSize) { + TRope res; + for (size_t i = 0; i < s.size(); ) { + size_t len = std::min(sliceSize, s.size() - i); + if (i % 2) { + auto str = s.substr(i, len); + res.Insert(res.End(), TRope( + TSharedData::Copy(str.data(), str.size()))); + } else { + res.Insert(res.End(), TRope(s.substr(i, len))); + } + i += len; + } + return res; + } + + TString RopeToString(const TRope& rope) { + TString res; + auto iter = rope.Begin(); + while (iter != rope.End()) { + res.append(iter.ContiguousData(), iter.ContiguousSize()); + iter.AdvanceToNextContiguousBlock(); + } + + UNIT_ASSERT_VALUES_EQUAL(rope.GetSize(), res.size()); + + TString temp = TString::Uninitialized(rope.GetSize()); + rope.Begin().ExtractPlainDataAndAdvance(temp.Detach(), temp.size()); + UNIT_ASSERT_VALUES_EQUAL(temp, res); + + return res; + } + + TString Text = "No elements are copied or moved, only the internal pointers of the list nodes are re-pointed."; + + } + + Y_UNIT_TEST_SUITE(TRopeSharedDataNativeBackend) { + + // Same tests as in TRope but with new CreateRope using TSharedData backend + + Y_UNIT_TEST(Leak) { + const size_t begin = 10, end = 20; + TRope rope = CreateRope(Text, 10); + rope.Erase(rope.Begin() + begin, rope.Begin() + end); + } + + Y_UNIT_TEST(BasicRange) { + TRope rope = CreateRope(Text, 10); + for (size_t begin = 0; begin < Text.size(); ++begin) { + for (size_t end = begin; end <= Text.size(); ++end) { + TRope::TIterator rBegin = rope.Begin() + begin; + TRope::TIterator rEnd = rope.Begin() + end; + UNIT_ASSERT_VALUES_EQUAL(RopeToString(TRope(rBegin, rEnd)), Text.substr(begin, end - begin)); + } + } + } + + Y_UNIT_TEST(Erase) { + for (size_t begin = 0; begin < Text.size(); ++begin) { + for (size_t end = begin; end <= Text.size(); ++end) { + TRope rope = CreateRope(Text, 10); + rope.Erase(rope.Begin() + begin, rope.Begin() + end); + TString text = Text; + text.erase(text.begin() + begin, text.begin() + end); + UNIT_ASSERT_VALUES_EQUAL(RopeToString(rope), text); + } + } + } + + Y_UNIT_TEST(Insert) { + TRope rope = CreateRope(Text, 10); + for (size_t begin = 0; begin < Text.size(); ++begin) { + for (size_t end = begin; end <= Text.size(); ++end) { + TRope part = TRope(rope.Begin() + begin, rope.Begin() + end); + for (size_t where = 0; where <= Text.size(); ++where) { + TRope x(rope); + x.Insert(x.Begin() + where, TRope(part)); + UNIT_ASSERT_VALUES_EQUAL(x.GetSize(), rope.GetSize() + part.GetSize()); + TString text = Text; + text.insert(text.begin() + where, Text.begin() + begin, Text.begin() + end); + UNIT_ASSERT_VALUES_EQUAL(RopeToString(x), text); + } + } + } + } + + Y_UNIT_TEST(Extract) { + for (size_t begin = 0; begin < Text.size(); ++begin) { + for (size_t end = begin; end <= Text.size(); ++end) { + TRope rope = CreateRope(Text, 10); + TRope part = rope.Extract(rope.Begin() + begin, rope.Begin() + end); + TString text = Text; + text.erase(text.begin() + begin, text.begin() + end); + UNIT_ASSERT_VALUES_EQUAL(RopeToString(rope), text); + UNIT_ASSERT_VALUES_EQUAL(RopeToString(part), Text.substr(begin, end - begin)); + } + } + } + + Y_UNIT_TEST(EraseFront) { + for (size_t pos = 0; pos <= Text.size(); ++pos) { + TRope rope = CreateRope(Text, 10); + rope.EraseFront(pos); + UNIT_ASSERT_VALUES_EQUAL(RopeToString(rope), Text.substr(pos)); + } + } + + Y_UNIT_TEST(EraseBack) { + for (size_t pos = 0; pos <= Text.size(); ++pos) { + TRope rope = CreateRope(Text, 10); + rope.EraseBack(pos); + UNIT_ASSERT_VALUES_EQUAL(RopeToString(rope), Text.substr(0, Text.size() - pos)); + } + } + + Y_UNIT_TEST(ExtractFront) { + for (size_t step = 1; step <= Text.size(); ++step) { + TRope rope = CreateRope(Text, 10); + TRope out; + while (const size_t len = Min(step, rope.GetSize())) { + rope.ExtractFront(len, &out); + UNIT_ASSERT(rope.GetSize() + out.GetSize() == Text.size()); + UNIT_ASSERT_VALUES_EQUAL(RopeToString(out), Text.substr(0, out.GetSize())); + } + } + } + + Y_UNIT_TEST(ExtractFrontPlain) { + for (size_t step = 1; step <= Text.size(); ++step) { + TRope rope = CreateRope(Text, 10); + TString buffer = Text; + auto it = rope.Begin(); + size_t remain = rope.GetSize(); + while (const size_t len = Min(step, remain)) { + TString data = TString::Uninitialized(len); + it.ExtractPlainDataAndAdvance(data.Detach(), data.size()); + UNIT_ASSERT_VALUES_EQUAL(data, buffer.substr(0, len)); + UNIT_ASSERT_VALUES_EQUAL(RopeToString(TRope(it, rope.End())), buffer.substr(len)); + buffer = buffer.substr(len); + remain -= len; + } + } + } + + Y_UNIT_TEST(Glueing) { + TRope rope = CreateRope(Text, 10); + for (size_t begin = 0; begin <= Text.size(); ++begin) { + for (size_t end = begin; end <= Text.size(); ++end) { + TString repr = rope.DebugString(); + TRope temp = rope.Extract(rope.Position(begin), rope.Position(end)); + rope.Insert(rope.Position(begin), std::move(temp)); + UNIT_ASSERT_VALUES_EQUAL(repr, rope.DebugString()); + UNIT_ASSERT_VALUES_EQUAL(RopeToString(rope), Text); + } + } + } + + Y_UNIT_TEST(IterWalk) { + TRope rope = CreateRope(Text, 10); + for (size_t step1 = 0; step1 <= rope.GetSize(); ++step1) { + for (size_t step2 = 0; step2 <= step1; ++step2) { + TRope::TConstIterator iter = rope.Begin(); + iter += step1; + iter -= step2; + UNIT_ASSERT(iter == rope.Position(step1 - step2)); + } + } + } + + Y_UNIT_TEST(Compare) { + auto check = [](const TString& x, const TString& y) { + const TRope xRope = CreateRope(x, 7); + const TRope yRope = CreateRope(y, 11); + UNIT_ASSERT_VALUES_EQUAL(xRope == yRope, x == y); + UNIT_ASSERT_VALUES_EQUAL(xRope != yRope, x != y); + UNIT_ASSERT_VALUES_EQUAL(xRope < yRope, x < y); + UNIT_ASSERT_VALUES_EQUAL(xRope <= yRope, x <= y); + UNIT_ASSERT_VALUES_EQUAL(xRope > yRope, x > y); + UNIT_ASSERT_VALUES_EQUAL(xRope >= yRope, x >= y); + }; + + TVector<TString> pool; + for (size_t k = 0; k < 10; ++k) { + size_t len = RandomNumber<size_t>(100) + 100; + TString s = TString::Uninitialized(len); + char *p = s.Detach(); + for (size_t j = 0; j < len; ++j) { + *p++ = RandomNumber<unsigned char>(); + } + pool.push_back(std::move(s)); + } + + for (const TString& x : pool) { + for (const TString& y : pool) { + check(x, y); + } + } + } + + // Specific TSharedDataRopeBackend tests + + Y_UNIT_TEST(RopeOnlyBorrows) { + TSharedData data = TSharedData::Copy(Text.data(), Text.size()); + { + TRope rope; + rope.Insert(rope.End(), TRope(data)); + UNIT_ASSERT(data.IsShared()); + TSharedData dataCopy = data; + UNIT_ASSERT(dataCopy.IsShared()); + UNIT_ASSERT_EQUAL(dataCopy.data(), data.data()); + rope.Insert(rope.End(), TRope(data)); + rope.Insert(rope.End(), TRope(data)); + dataCopy.Trim(10); + UNIT_ASSERT_EQUAL(rope.GetSize(), data.size() * 3); + } + UNIT_ASSERT(data.IsPrivate()); + } + } + +} // namespace NActors diff --git a/library/cpp/actors/util/shared_data_rope_backend.h b/library/cpp/actors/util/shared_data_rope_backend.h index 3add6afd27f..312dab91123 100644 --- a/library/cpp/actors/util/shared_data_rope_backend.h +++ b/library/cpp/actors/util/shared_data_rope_backend.h @@ -1,12 +1,12 @@ #pragma once -#include <library/cpp/actors/util/rope.h> +#include <library/cpp/actors/util/contiguous_data.h> #include "shared_data.h" namespace NActors { -class TRopeSharedDataBackend : public IRopeChunkBackend { +class TRopeSharedDataBackend : public IContiguousChunk { TSharedData Buffer; public: @@ -14,18 +14,18 @@ public: : Buffer(std::move(buffer)) {} - TData GetData() const override { + TContiguousSpan GetData() const override { return {Buffer.data(), Buffer.size()}; } - TMutData GetDataMut() override { + TMutableContiguousSpan GetDataMut() override { if(Buffer.IsShared()) { Buffer = TSharedData::Copy(Buffer.data(), Buffer.size()); } return {Buffer.mutable_data(), Buffer.size()}; } - TMutData UnsafeGetDataMut() override { + TMutableContiguousSpan UnsafeGetDataMut() override { return {const_cast<char *>(Buffer.data()), Buffer.size()}; } diff --git a/library/cpp/actors/util/ut_helpers.h b/library/cpp/actors/util/ut_helpers.h new file mode 100644 index 00000000000..d3fe873233e --- /dev/null +++ b/library/cpp/actors/util/ut_helpers.h @@ -0,0 +1,12 @@ +#pragma once + +// calls TCallback for all args permutations including (id, id) +template <class TCallback, class... TArgs> +void Permutate(TCallback&& fn, TArgs&&... args) +{ + auto forAll = [&](auto& arg){ + (fn(std::forward<decltype(arg)>(arg), std::forward<decltype(args)>(args)), ...); + }; + + (forAll(std::forward<decltype(args)>(args)), ...); +} |