-
Notifications
You must be signed in to change notification settings - Fork 5
Convert thread storage from std::array to stable_vector with dynamic growth #21
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
base: timem
Are you sure you want to change the base?
Changes from all commits
e47f198
d6ffeb9
080b204
8c2afd3
0eb3c2e
fad254d
858cfc7
5400e13
b82ec24
469eced
5780cc7
4897dda
8b01785
b5e41aa
4daa81b
2dd0511
29037f3
24407d3
696a160
2624144
3bdd03a
079309f
f3f2b94
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1 +1 @@ | ||
| 4.0.0rc0 | ||
| 4.0.1rc0 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -39,8 +39,10 @@ | |
| #include "timemory/storage/types.hpp" | ||
| #include "timemory/variadic/types.hpp" | ||
|
|
||
| #include <atomic> | ||
| #include <functional> | ||
| #include <iosfwd> | ||
| #include <mutex> | ||
| #include <type_traits> | ||
| #include <utility> | ||
|
|
||
|
|
@@ -798,6 +800,109 @@ struct get_depth | |
| } | ||
| }; | ||
| // | ||
| // | ||
| /// \struct tim::operation::stable_storage | ||
| /// \tparam StorageType The storage pointer type (e.g., storage<T>*) | ||
| /// \tparam ChunkSize Size of each chunk (default: max_threads) | ||
| /// | ||
| /// \brief A pointer-stable storage container using vector of unique_ptr to arrays. | ||
| /// Unlike std::vector, existing element pointers remain valid during growth. | ||
| template <typename StorageType, size_t ChunkSize> | ||
| class stable_storage | ||
| { | ||
| public: | ||
| using value_type = StorageType; | ||
| using chunk_type = std::array<StorageType, ChunkSize>; | ||
| using chunk_ptr = std::unique_ptr<chunk_type>; | ||
|
|
||
| stable_storage() = default; | ||
|
|
||
| explicit stable_storage(size_t initial_size, StorageType default_val = StorageType{}) | ||
| { | ||
| reserve(initial_size); | ||
| for(size_t i = 0; i < initial_size; ++i) | ||
| (*this)[i] = default_val; | ||
| m_size = initial_size; | ||
| } | ||
|
|
||
| StorageType& operator[](size_t idx) | ||
| { | ||
| ensure_capacity(idx); | ||
| return (*m_chunks[idx / ChunkSize])[idx % ChunkSize]; | ||
| } | ||
|
|
||
| StorageType& at(size_t idx) | ||
| { | ||
| ensure_capacity(idx); | ||
| return (*m_chunks[idx / ChunkSize])[idx % ChunkSize]; | ||
| } | ||
|
|
||
| size_t size() const { return m_size; } | ||
| size_t capacity() const { return m_chunks.size() * ChunkSize; } | ||
|
|
||
| void reserve(size_t new_cap) | ||
| { | ||
| while(capacity() < new_cap) | ||
| add_chunk(); | ||
| } | ||
|
|
||
| void ensure_capacity(size_t idx) | ||
| { | ||
| if(idx >= capacity()) | ||
| { | ||
| std::lock_guard<std::mutex> lock(m_mutex); | ||
| while(idx >= capacity()) | ||
| add_chunk(); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. After |
||
| } | ||
| if(idx >= m_size) | ||
| m_size = idx + 1; | ||
| } | ||
|
|
||
| private: | ||
| void add_chunk() | ||
| { | ||
| auto chunk = std::make_unique<chunk_type>(); | ||
| chunk->fill(StorageType{}); | ||
| m_chunks.push_back(std::move(chunk)); | ||
| } | ||
|
|
||
| std::vector<chunk_ptr> m_chunks{}; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Usage of |
||
| size_t m_size{0}; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| std::mutex m_mutex{}; | ||
| }; | ||
|
|
||
| /// \struct tim::operation::dynamic_storage_base | ||
| /// \tparam StorageType The storage pointer type (e.g., storage<T>*) | ||
| /// \tparam max_threads Initial capacity for the storage array | ||
| /// | ||
| /// \brief A reusable base class for thread-safe dynamic storage management. | ||
| /// Uses stable_storage which maintains pointer stability during growth. | ||
| template <typename StorageType, size_t max_threads> | ||
| struct dynamic_storage_base | ||
| { | ||
| using storage_array_t = stable_storage<StorageType, max_threads>; | ||
|
|
||
| /// @brief Returns the current capacity (thread-safe read-only access) | ||
| static size_t get_capacity() | ||
| { | ||
| return get_storage().capacity(); | ||
| } | ||
|
|
||
| protected: | ||
| static storage_array_t& get_storage() | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| { | ||
| static storage_array_t instance(max_threads, nullptr); | ||
| return instance; | ||
| } | ||
|
|
||
| static void ensure_capacity(storage_array_t& _v, size_t _idx) | ||
| { | ||
| _v.ensure_capacity(_idx); | ||
| } | ||
| }; | ||
| // | ||
| //--------------------------------------------------------------------------------------// | ||
|
|
||
| //--------------------------------------------------------------------------------------// | ||
| // | ||
| template <typename T> | ||
|
|
@@ -810,29 +915,30 @@ struct get_storage; | |
| /// the data storage structure for a component which should be updated for | ||
| /// aggregation/logging. | ||
| template <typename T> | ||
| struct set_storage | ||
| struct set_storage : public dynamic_storage_base<storage<T>*, TIMEMORY_MAX_THREADS> | ||
| { | ||
| friend struct get_storage<T>; | ||
| static constexpr size_t max_threads = 4096; | ||
| static constexpr size_t max_threads = TIMEMORY_MAX_THREADS; | ||
| using type = T; | ||
| using storage_array_t = std::array<storage<type>*, max_threads>; | ||
| using base_type = dynamic_storage_base<storage<T>*, max_threads>; | ||
| using storage_array_t = typename base_type::storage_array_t; | ||
|
|
||
| TIMEMORY_DEFAULT_OBJECT(set_storage) | ||
|
|
||
| TIMEMORY_INLINE auto operator()(storage<type>* _storage, size_t _idx) const | ||
| { | ||
| base_type::ensure_capacity(get(), _idx); | ||
| get().at(_idx) = static_cast<storage<type>*>(_storage); | ||
| } | ||
|
|
||
| TIMEMORY_INLINE auto operator()(type& _obj, size_t _idx) const | ||
| { | ||
| base_type::ensure_capacity(get(), _idx); | ||
| get().at(_idx) = static_cast<storage<type>*>(_obj.get_storage()); | ||
| } | ||
|
|
||
| private: | ||
| static storage_array_t& get() | ||
| { | ||
| static storage_array_t _v = { nullptr }; | ||
| static storage_array_t _v(max_threads, nullptr); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should return |
||
| return _v; | ||
| } | ||
| }; | ||
|
|
@@ -865,6 +971,9 @@ struct get_storage | |
|
|
||
| TIMEMORY_INLINE auto operator()(size_t _idx) const | ||
| { | ||
| // Thread-safe read using atomic capacity | ||
| if(_idx >= operation::set_storage<T>::get_capacity()) | ||
| return static_cast<storage<type>*>(nullptr); | ||
| return operation::set_storage<T>::get().at(_idx); | ||
| } | ||
|
|
||
|
|
||
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.
reservehere is also not thread safe, should be guarded with lock.