Skip to content

Commit 2a1d8fa

Browse files
committed
Update CMakeLists.txt and expected.h, remove unused files, and improve thread safety in block_pool.cpp
1 parent f615f20 commit 2a1d8fa

File tree

8 files changed

+438
-126
lines changed

8 files changed

+438
-126
lines changed

CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ set(CMAKE_POSITION_INDEPENDENT_CODE ON)
1111
set(CMAKE_CXX_STANDARD 17)
1212

1313
if (MSVC)
14-
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /fsanitize=address /Zi")
14+
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /Zi")
1515
set(CompilerFlags
1616
CMAKE_CXX_FLAGS
1717
CMAKE_CXX_FLAGS_DEBUG

benchmark/benchmark_new.cpp

Lines changed: 325 additions & 0 deletions
Large diffs are not rendered by default.

include/libimp/expected.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -322,7 +322,7 @@ R or_else(E &&exp, F &&f) {
322322
*/
323323
template <typename T, typename E>
324324
class expected : public detail_expected::storage<typename std::remove_cv<T>::type, E> {
325-
public:
325+
public:
326326
using value_type = typename std::remove_cv<T>::type;
327327
using error_type = E;
328328

include/libpmr/def.h

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,6 @@
88

99
#include <cstddef>
1010

11-
#include "libimp/aligned.h"
12-
1311
#define LIBPMR pmr
1412
#define LIBPMR_NAMESPACE_BEG_ namespace LIBPMR {
1513
#define LIBPMR_NAMESPACE_END_ }
@@ -20,7 +18,6 @@ LIBPMR_NAMESPACE_BEG_
2018

2119
enum : std::size_t {
2220
central_cache_default_size = 1024 * 1024, ///< 1MB
23-
regular_head_size = ::LIBIMP::round_up(sizeof(std::size_t), alignof(std::max_align_t)),
2421
};
2522

2623
LIBPMR_NAMESPACE_END_

include/libpmr/new.h

Lines changed: 50 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
#pragma once
88

99
#include <cstddef>
10-
#include <unordered_map>
1110
#include <algorithm>
1211

1312
#include "libimp/aligned.h"
@@ -22,6 +21,18 @@
2221

2322
LIBPMR_NAMESPACE_BEG_
2423

24+
/// \brief Defines the memory block collector interface.
25+
class LIBIMP_EXPORT block_collector {
26+
public:
27+
virtual ~block_collector() noexcept = default;
28+
virtual void deallocate(void *p) noexcept = 0;
29+
};
30+
31+
using get_block_collector_t = block_collector *(*)() noexcept;
32+
33+
static constexpr std::size_t regular_head_size
34+
= ::LIBIMP::round_up(sizeof(get_block_collector_t), alignof(std::max_align_t));
35+
2536
/// \brief Select the incremental level based on the size.
2637
constexpr inline std::size_t regular_level(std::size_t s) noexcept {
2738
return (s <= 128 ) ? 0 :
@@ -32,52 +43,25 @@ constexpr inline std::size_t regular_level(std::size_t s) noexcept {
3243

3344
/// \brief Calculates the appropriate memory block size based on the increment level and size.
3445
constexpr inline std::size_t regular_sizeof_impl(std::size_t l, std::size_t s) noexcept {
35-
return (l == 0) ? std::max<std::size_t>(::LIBIMP::round_up<std::size_t>(s, 8), regular_head_size) :
46+
return (l == 0) ? ::LIBIMP::round_up<std::size_t>(s, regular_head_size) :
3647
(l == 1) ? ::LIBIMP::round_up<std::size_t>(s, 128 ) :
3748
(l == 2) ? ::LIBIMP::round_up<std::size_t>(s, 1024) :
3849
(l == 3) ? ::LIBIMP::round_up<std::size_t>(s, 8192) : (std::numeric_limits<std::size_t>::max)();
3950
}
4051

4152
/// \brief Calculates the appropriate memory block size based on the size.
42-
constexpr inline std::size_t regular_sizeof(std::size_t s) noexcept {
53+
constexpr inline std::size_t regular_sizeof_impl(std::size_t s) noexcept {
4354
return regular_sizeof_impl(regular_level(s), s);
4455
}
4556

4657
/// \brief Calculates the appropriate memory block size based on the specific type.
4758
template <typename T>
4859
constexpr inline std::size_t regular_sizeof() noexcept {
49-
return regular_sizeof(regular_head_size + sizeof(T));
60+
return regular_sizeof_impl(regular_head_size + sizeof(T));
5061
}
5162

52-
/// \brief Defines the memory block collector interface.
53-
class LIBIMP_EXPORT block_collector {
54-
public:
55-
virtual ~block_collector() noexcept = default;
56-
virtual void deallocate(void *p) noexcept = 0;
57-
};
58-
59-
/// \brief Gets all block pools of the thread cache.
60-
LIBIMP_EXPORT auto get_thread_block_pool_map() noexcept
61-
-> std::unordered_map<std::size_t, block_collector *> &;
62-
6363
/// \brief Defines block pool memory resource based on block pool.
6464
template <std::size_t BlockSize, std::size_t BlockPoolExpansion>
65-
class block_pool_resource;
66-
67-
/// \brief Memory block collector of unknown size.
68-
/// \note This memory resource is only used to temporarily collect memory blocks
69-
/// that cannot find a suitable block pool memory resource.
70-
template <>
71-
class block_pool_resource<0, 0> : public block_pool<0, 0>
72-
, public block_collector {
73-
public:
74-
void deallocate(void *p) noexcept override {
75-
block_pool<0, 0>::deallocate(p);
76-
}
77-
};
78-
79-
/// \brief A block pool memory resource for a block of memory of a specific size.
80-
template <std::size_t BlockSize, std::size_t BlockPoolExpansion>
8165
class block_pool_resource : public block_pool<BlockSize, BlockPoolExpansion>
8266
, public block_collector {
8367

@@ -88,99 +72,59 @@ class block_pool_resource : public block_pool<BlockSize, BlockPoolExpansion>
8872
}
8973

9074
public:
91-
static block_pool_resource *get() noexcept;
75+
static block_collector *get() noexcept {
76+
thread_local block_pool_resource instance;
77+
return &instance;
78+
}
9279

9380
using base_t::base_t;
9481

9582
void *allocate(std::size_t /*bytes*/, std::size_t /*alignment*/ = alignof(std::max_align_t)) noexcept {
9683
void *p = base_t::allocate();
97-
p = ::LIBIMP::construct<std::size_t>(p, BlockSize);
98-
return reinterpret_cast<::LIBIMP::byte *>(p) + regular_head_size;
84+
*static_cast<get_block_collector_t *>(p) = get;
85+
return static_cast<::LIBIMP::byte *>(p) + regular_head_size;
9986
}
10087

10188
void deallocate(void *p, std::size_t /*bytes*/, std::size_t /*alignment*/ = alignof(std::max_align_t)) noexcept {
102-
p = reinterpret_cast<::LIBIMP::byte *>(p) - regular_head_size;
103-
auto r_size = *static_cast<std::size_t *>(p);
104-
if (r_size <= BlockSize) {
89+
p = static_cast<::LIBIMP::byte *>(p) - regular_head_size;
90+
auto g = *static_cast<get_block_collector_t *>(p);
91+
if (g == get) {
10592
base_t::deallocate(p);
10693
return;
10794
}
108-
// When the actual size exceeds the current memory block size,
109-
// try to find a suitable pool among all memory block pools for this thread.
110-
auto &map = get_thread_block_pool_map();
111-
auto it = map.find(r_size);
112-
if ((it == map.end()) || (it->second == nullptr)) {
113-
block_pool_resource<0, 0> *bp = nullptr;
114-
LIBIMP_TRY {
115-
// If the corresponding memory resource cannot be found,
116-
// create a temporary general-purpose block pool to deallocate memory.
117-
it = map.emplace(r_size, bp = new block_pool_resource<0, 0>).first;
118-
} LIBIMP_CATCH(...) {
119-
// If the memory resource cannot be created,
120-
// store the pointer directly to avoid leakage.
121-
delete bp;
122-
base_t::deallocate(p);
123-
return;
124-
}
125-
}
126-
it->second->deallocate(p);
95+
g()->deallocate(p);
12796
}
12897
};
12998

130-
template <std::size_t BlockSize, std::size_t BlockPoolExpansion>
131-
auto block_pool_resource<BlockSize, BlockPoolExpansion>::get() noexcept
132-
-> block_pool_resource<BlockSize, BlockPoolExpansion> * {
133-
thread_local block_pool_resource *pi = nullptr;
134-
if (pi != nullptr) {
135-
return pi;
136-
}
137-
// Create a new block pool resource for this thread.
138-
auto &map = get_thread_block_pool_map();
139-
auto it = map.find(BlockSize);
140-
if ((it != map.end()) && (it->second != nullptr)) {
141-
// If there are existing block pool resources in the thread cache,
142-
// a new block pool resource is constructed based on it and the cache is updated.
143-
auto *bp = static_cast <block_pool<0, 0> *>(
144-
dynamic_cast<block_pool_resource<0, 0> *>(it->second));
145-
if (bp == nullptr) {
146-
return nullptr;
147-
}
148-
thread_local block_pool_resource instance(std::move(*bp));
149-
delete static_cast<block_pool_resource<0, 0> *>(bp);
150-
it->second = pi = &instance;
151-
return pi;
152-
} else {
153-
// If there are no existing block pool resources in the thread cache,
154-
// the thread local storage instance is constructed and the pointer is cached.
155-
thread_local block_pool_resource instance;
156-
LIBIMP_TRY {
157-
map.emplace(BlockSize, pi = &instance);
158-
return pi;
159-
} LIBIMP_CATCH(...) {
160-
return nullptr;
161-
}
162-
}
163-
}
99+
/// \brief Different increment levels match different chunk sizes.
100+
/// 512 means that 512 consecutive memory blocks are allocated at a time, and the block size is N.
101+
template <std::size_t L>
102+
constexpr static std::size_t block_pool_expansion = 0;
103+
104+
template <> constexpr static std::size_t block_pool_expansion<0> = 512;
105+
template <> constexpr static std::size_t block_pool_expansion<1> = 256;
106+
template <> constexpr static std::size_t block_pool_expansion<2> = 128;
107+
template <> constexpr static std::size_t block_pool_expansion<3> = 64;
164108

165-
/// \brief Match the appropriate memory block resources
166-
/// according to the size of the specification.
109+
/// \brief Match the appropriate memory block resources according to the size of the specification.
167110
template <std::size_t N, std::size_t L = regular_level(N)>
168-
class regular_resource : public new_delete_resource {};
111+
struct regular_resource {
112+
static auto *get() noexcept {
113+
using block_poll_resource_t = block_pool_resource<N, block_pool_expansion<L>>;
114+
return dynamic_cast<block_poll_resource_t *>(block_poll_resource_t::get());
115+
}
116+
};
169117

170-
/// \brief Different increment levels match different chunk sizes.
171-
/// 512 means that 512 consecutive memory blocks are allocated at a time, and the block size is N.
172-
template <std::size_t N> class regular_resource<N, 0> : public block_pool_resource<N, 512> {};
173-
template <std::size_t N> class regular_resource<N, 1> : public block_pool_resource<N, 256> {};
174-
template <std::size_t N> class regular_resource<N, 2> : public block_pool_resource<N, 128> {};
175-
template <std::size_t N> class regular_resource<N, 3> : public block_pool_resource<N, 64 > {};
118+
template <std::size_t N>
119+
struct regular_resource<N, 4> : new_delete_resource {};
176120

177121
/// \brief Creates an object based on the specified type and parameters with block pool resource.
178122
/// \note This function is thread-safe.
179123
template <typename T, typename... A>
180124
T *new$(A &&... args) noexcept {
181-
auto *mem_res = regular_resource<regular_sizeof<T>()>::get();
182-
if (mem_res == nullptr) return nullptr;
183-
return ::LIBIMP::construct<T>(mem_res->allocate(sizeof(T), alignof(T)), std::forward<A>(args)...);
125+
auto *res = regular_resource<regular_sizeof<T>()>::get();
126+
if (res == nullptr) return nullptr;
127+
return ::LIBIMP::construct<T>(res->allocate(sizeof(T), alignof(T)), std::forward<A>(args)...);
184128
}
185129

186130
/// \brief Destroys object previously allocated by the `new$` and releases obtained memory area.
@@ -190,13 +134,13 @@ template <typename T>
190134
void delete$(T *p) noexcept {
191135
if (p == nullptr) return;
192136
::LIBIMP::destroy(p);
193-
auto *mem_res = regular_resource<regular_sizeof<T>()>::get();
194-
if (mem_res == nullptr) return;
137+
auto *res = regular_resource<regular_sizeof<T>()>::get();
138+
if (res == nullptr) return;
195139
#if defined(LIBIMP_CC_MSVC_2015)
196140
// `alignof` of vs2015 requires that type must be able to be instantiated.
197-
mem_res->deallocate(p, sizeof(T));
141+
res->deallocate(p, sizeof(T));
198142
#else
199-
mem_res->deallocate(p, sizeof(T), alignof(T));
143+
res->deallocate(p, sizeof(T), alignof(T));
200144
#endif
201145
}
202146

src/libpmr/block_pool.cpp

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,41 @@
11

2+
#include <mutex>
3+
4+
#include "libimp/detect_plat.h"
5+
26
#include "libpmr/block_pool.h"
37
#include "libpmr/monotonic_buffer_resource.h"
48

59
LIBPMR_NAMESPACE_BEG_
610

11+
class thread_safe_resource : public monotonic_buffer_resource {
12+
public:
13+
thread_safe_resource(::LIBIMP::span<::LIBIMP::byte> buffer) noexcept
14+
: monotonic_buffer_resource(buffer) {}
15+
16+
~thread_safe_resource() noexcept {
17+
LIBIMP_UNUSED std::lock_guard<std::mutex> lock(mutex_);
18+
monotonic_buffer_resource::release();
19+
}
20+
21+
void *allocate(std::size_t bytes, std::size_t alignment) noexcept {
22+
LIBIMP_UNUSED std::lock_guard<std::mutex> lock(mutex_);
23+
return monotonic_buffer_resource::allocate(bytes, alignment);
24+
}
25+
26+
void deallocate(void *p, std::size_t bytes, std::size_t alignment) noexcept {
27+
LIBIMP_UNUSED std::lock_guard<std::mutex> lock(mutex_);
28+
monotonic_buffer_resource::deallocate(p, bytes, alignment);
29+
}
30+
31+
private:
32+
std::mutex mutex_;
33+
};
34+
735
allocator &central_cache_allocator() noexcept {
8-
static std::array<::LIBIMP::byte, central_cache_default_size> buffer;
9-
static monotonic_buffer_resource mr(buffer);
10-
static allocator a(&mr);
36+
static std::array<::LIBIMP::byte, central_cache_default_size> buf;
37+
static thread_safe_resource res(buf);
38+
static allocator a(&res);
1139
return a;
1240
}
1341

src/libpmr/new.cpp

Lines changed: 0 additions & 12 deletions
This file was deleted.

test/pmr/test_pmr_new.cpp

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#include <array>
44
#include <cstring>
55
#include <cstddef>
6+
#include <thread>
67

78
#include "gtest/gtest.h"
89

@@ -114,3 +115,32 @@ TEST(pmr_new, delete$null) {
114115
pmr::delete$(p);
115116
SUCCEED();
116117
}
118+
119+
TEST(pmr_new, multi_thread) {
120+
std::array<std::thread, 16> threads;
121+
for (auto &t : threads) {
122+
t = std::thread([] {
123+
for (int i = 0; i < 10000; ++i) {
124+
auto p = pmr::new$<int>();
125+
*p = i;
126+
pmr::delete$(p);
127+
}
128+
std::array<void *, 10000> pts;
129+
for (int i = 0; i < 10000; ++i) {
130+
auto p = pmr::new$<std::array<char, 10>>();
131+
pts[i] = p;
132+
std::memset(p, i, sizeof(std::array<char, 10>));
133+
}
134+
for (int i = 0; i < 10000; ++i) {
135+
std::array<char, 10> tmp;
136+
std::memset(&tmp, i, sizeof(std::array<char, 10>));
137+
ASSERT_EQ(std::memcmp(pts[i], &tmp, sizeof(std::array<char, 10>)), 0);
138+
pmr::delete$(static_cast<std::array<char, 10> *>(pts[i]));
139+
}
140+
});
141+
}
142+
for (auto &t : threads) {
143+
t.join();
144+
}
145+
SUCCEED();
146+
}

0 commit comments

Comments
 (0)