diff options
| author | kulikov <[email protected]> | 2025-11-25 01:07:59 +0300 |
|---|---|---|
| committer | kulikov <[email protected]> | 2025-11-25 01:25:22 +0300 |
| commit | f688bd5f5a6b127269d329a997ff5945cbcd5d87 (patch) | |
| tree | 15564ded86e7d8f8a8f2b59055cca0269d6984ee /library/cpp/threading/thread_local | |
| parent | 7af73a3de3b9a9faa4a8654f5a2b62adf21dded5 (diff) | |
TThreadLocalValue over standard thread_locals
Current TThreadLocal implementation doesn't destroy objects on thread exit (so we can't replace thread\_local tls value with it, and with TGenericLocalValue too), standard thread\_local values can't be class members. So try to reimplement TThreadLocalValue:
- instead of adressing by thread id, address objects by uniq object id in thread\_local deque of all objects of current type;
- use hashmap of all existing perthread deques to destroy objects with their TThreadLocal;
- try to make object ids flat;
- no complex lookup inside and no contention between tls.Get calls;
- ShortLivedThreads ut now works;
- some fixes in Trace ut, make threads outlive tls value;
- benchmark results <https://nda.ya.ru/t/agWa9PW67NYAAK>, looks better than all others;
- probably should switch to this implementation by default and remove old.
commit_hash:262ce4d363751f577373a452a0c2c84d1fe5b79c
Diffstat (limited to 'library/cpp/threading/thread_local')
| -rw-r--r-- | library/cpp/threading/thread_local/thread_local.h | 122 |
1 files changed, 122 insertions, 0 deletions
diff --git a/library/cpp/threading/thread_local/thread_local.h b/library/cpp/threading/thread_local/thread_local.h index 15cb43d37f3..af4d7a07cf2 100644 --- a/library/cpp/threading/thread_local/thread_local.h +++ b/library/cpp/threading/thread_local/thread_local.h @@ -1,6 +1,8 @@ #pragma once +#include <util/generic/deque.h> #include <util/generic/hash.h> +#include <util/generic/hash_set.h> #include <util/generic/maybe.h> #include <util/generic/ptr.h> #include <util/generic/vector.h> @@ -91,6 +93,7 @@ enum class EThreadLocalImpl { HotSwap, SkipList, ForwardList, + StdThreadLocal // completely different implementation over thread_local keyword, correctly destroys objects on thread finish }; namespace NDetail { @@ -122,6 +125,125 @@ private: mutable std::array<TStorage, NumShards> Shards_; }; +template <typename T, size_t NumShards> +class TThreadLocalValue<T, EThreadLocalImpl::StdThreadLocal, NumShards> : private TNonCopyable { +public: + ~TThreadLocalValue() { + AllValues_.Destroy(ObjectId_); + FlatIds.Put(ObjectId_); + } + + template <typename ...ConstructArgs> + T& GetRef(ConstructArgs&& ...args) const { + return *Get(std::forward<ConstructArgs>(args)...); + } + + template <typename ...ConstructArgs> + T* Get(ConstructArgs&& ...args) const { + auto& values = Values_.Storage; + + with_lock (Values_.Lock) { + if (ObjectId_ >= values.size()) { + values.resize(ObjectId_ + 1); + } + } + + TMaybe<T>& v = values[ObjectId_]; + if (!v.Defined()) { + v.ConstructInPlace(std::forward<ConstructArgs>(args)...); + } + + return &*v; + } +private: + struct TAllValues; + + struct TPerThreadValues { + TAdaptiveLock Lock; + TDeque<TMaybe<T>> Storage; + public: + explicit TPerThreadValues(TAllValues* allValues) + : AllValues_(allValues) + { + AllValues_->Add(this); + } + + ~TPerThreadValues() { + AllValues_->Remove(this); + } + private: + TAllValues* const AllValues_; + }; + + class TFlatIdGenerator { + public: + ui32 Get() { + with_lock (Lock_) { + if (Free_.empty()) { + return MaxUnused_++; + } else { + ui32 free = Free_.back(); + Free_.pop_back(); + return free; + } + } + } + + void Put(ui32 id) { + with_lock (Lock_) { + Free_.push_back(id); + } + } + private: + TAdaptiveLock Lock_; + TVector<ui32> Free_; + ui32 MaxUnused_ = 0; + }; + + struct TAllValues { + public: + void Add(TPerThreadValues* v) { + with_lock (Lock_) { + Ptrs_.insert(v); + } + } + + void Remove(TPerThreadValues* v) { + with_lock (Lock_) { + Ptrs_.erase(v); + } + } + + void Destroy(ui32 objectId) { + with_lock (Lock_) { + for (auto* v : Ptrs_) { + TMaybe<T>* toDestroy = nullptr; + with_lock (v->Lock) { + if (objectId < v->Storage.size()) { + toDestroy = &v->Storage[objectId]; + } + } + + if (toDestroy) { + toDestroy->Clear(); + } + } + } + } + private: + TAdaptiveLock Lock_; + THashSet<TPerThreadValues*> Ptrs_; + }; + +private: + static inline TFlatIdGenerator FlatIds; + const ui32 ObjectId_ = FlatIds.Get(); + + static inline TAllValues AllValues_; + static inline thread_local TPerThreadValues Values_{&AllValues_}; +}; + + namespace NDetail { template <typename T, size_t NumShards> |
