Skip to content

Commit 32c2be2

Browse files
committed
Refactoring the generic memory allocator
1 parent 2d38a6a commit 32c2be2

File tree

6 files changed

+184
-157
lines changed

6 files changed

+184
-157
lines changed

include/libipc/imp/generic.h

+5-3
Original file line numberDiff line numberDiff line change
@@ -285,15 +285,17 @@ namespace detail_horrible_cast {
285285

286286
template <typename T, typename U>
287287
union temp {
288-
std::decay_t<U> in;
288+
U in;
289289
T out;
290290
};
291291

292292
} // namespace detail_horrible_cast
293293

294294
template <typename T, typename U>
295-
constexpr T horrible_cast(U &&in) noexcept {
296-
return detail_horrible_cast::temp<T, U>{std::forward<U>(in)}.out;
295+
constexpr auto horrible_cast(U &&in) noexcept
296+
-> typename std::enable_if<std::is_trivially_copyable<T>::value
297+
&& std::is_trivially_copyable<std::decay_t<U>>::value, T>::type {
298+
return detail_horrible_cast::temp<T, std::decay_t<U>>{std::forward<U>(in)}.out;
297299
}
298300

299301
} // namespace ipc

include/libipc/mem/container_allocator.h

+17-3
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
#include <cstddef>
99
#include <utility>
10+
#include <limits>
1011

1112
#include "libipc/imp/uninitialized.h"
1213
#include "libipc/mem/new.h"
@@ -56,19 +57,32 @@ class container_allocator {
5657
container_allocator& operator=(container_allocator &&) noexcept = default;
5758

5859
constexpr size_type max_size() const noexcept {
59-
return 1;
60+
return (std::numeric_limits<size_type>::max)() / sizeof(value_type);
6061
}
6162

6263
pointer allocate(size_type count) noexcept {
6364
if (count == 0) return nullptr;
6465
if (count > this->max_size()) return nullptr;
65-
return mem::$new<value_type>();
66+
if (count == 1) {
67+
return mem::$new<value_type>();
68+
} else {
69+
void *p = mem::alloc(sizeof(value_type) * count);
70+
if (p == nullptr) return nullptr;
71+
for (std::size_t i = 0; i < count; ++i) {
72+
std::ignore = ipc::construct<value_type>(static_cast<byte *>(p) + sizeof(value_type) * i);
73+
}
74+
return static_cast<pointer>(p);
75+
}
6676
}
6777

6878
void deallocate(pointer p, size_type count) noexcept {
6979
if (count == 0) return;
7080
if (count > this->max_size()) return;
71-
mem::$delete(p);
81+
if (count == 1) {
82+
mem::$delete(p);
83+
} else {
84+
mem::free(ipc::destroy_n(p, count), sizeof(value_type) * count);
85+
}
7286
}
7387

7488
template <typename... P>

include/libipc/mem/new.h

+41-126
Original file line numberDiff line numberDiff line change
@@ -26,133 +26,43 @@ namespace mem {
2626
class LIBIPC_EXPORT block_collector {
2727
public:
2828
virtual ~block_collector() noexcept = default;
29-
virtual void recycle(void */*p*/) noexcept {}
29+
virtual void *allocate(std::size_t /*bytes*/) noexcept = 0;
30+
virtual void deallocate(void */*p*/, std::size_t /*bytes*/) noexcept = 0;
3031
};
3132

32-
#if defined(LIBIPC_CPP_17)
33-
using recycle_t = void (*)(void *p, void *o) noexcept;
34-
#else
35-
using recycle_t = void (*)(void *p, void *o);
36-
#endif
37-
38-
static constexpr std::size_t regular_head_size
39-
= round_up(sizeof(recycle_t), alignof(std::max_align_t));
40-
41-
/// \brief Select the incremental level based on the size.
42-
constexpr inline std::size_t regular_level(std::size_t s) noexcept {
43-
return (s <= 128 ) ? 0 :
44-
(s <= 1024 ) ? 1 :
45-
(s <= 8192 ) ? 2 :
46-
(s <= 65536) ? 3 : 4;
47-
}
33+
/// \brief Matches the appropriate memory block resource based on a specified size.
34+
LIBIPC_EXPORT block_collector &get_regular_resource(std::size_t s) noexcept;
4835

49-
/// \brief Calculates the appropriate memory block size based on the increment level and size.
50-
constexpr inline std::size_t regular_sizeof_impl(std::size_t l, std::size_t s) noexcept {
51-
return (l == 0) ? round_up<std::size_t>(s, regular_head_size) :
52-
(l == 1) ? round_up<std::size_t>(s, 128 ) :
53-
(l == 2) ? round_up<std::size_t>(s, 1024) :
54-
(l == 3) ? round_up<std::size_t>(s, 8192) : (std::numeric_limits<std::size_t>::max)();
55-
}
56-
57-
/// \brief Calculates the appropriate memory block size based on the size.
58-
constexpr inline std::size_t regular_sizeof_impl(std::size_t s) noexcept {
59-
return regular_sizeof_impl(regular_level(s), s);
60-
}
61-
62-
/// \brief Calculates the appropriate memory block size based on the specific type.
63-
constexpr inline std::size_t regular_sizeof(std::size_t s) noexcept {
64-
return regular_sizeof_impl(regular_head_size + s);
65-
}
66-
template <typename T, std::size_t S = sizeof(T)>
67-
constexpr inline std::size_t regular_sizeof() noexcept {
68-
return regular_sizeof(S);
69-
}
36+
/// \brief Allocates storage with a size of at least bytes bytes.
37+
LIBIPC_EXPORT void *alloc(std::size_t bytes) noexcept;
38+
LIBIPC_EXPORT void free (void *p, std::size_t bytes) noexcept;
7039

71-
/// \brief Use block pools to handle memory less than 64K.
72-
template <std::size_t BlockSize, std::size_t BlockPoolExpansion>
73-
class block_resource_base : public block_pool<BlockSize, BlockPoolExpansion> {
74-
public:
75-
void *allocate(std::size_t /*bytes*/, std::size_t /*alignment*/) noexcept {
76-
return block_pool<BlockSize, BlockPoolExpansion>::allocate();
77-
}
78-
79-
void deallocate(void *p) noexcept {
80-
block_pool<BlockSize, BlockPoolExpansion>::deallocate(p);
81-
}
82-
};
83-
84-
/// \brief Use `new`/`delete` to handle memory larger than 64K.
85-
template <std::size_t BlockSize>
86-
class block_resource_base<BlockSize, 0> : public new_delete_resource {
87-
public:
88-
void *allocate(std::size_t bytes, std::size_t alignment) noexcept {
89-
return new_delete_resource::allocate(regular_head_size + bytes, alignment);
90-
}
91-
92-
void deallocate(void *p) noexcept {
93-
new_delete_resource::deallocate(p, regular_head_size);
94-
}
95-
};
96-
97-
/// \brief Defines block pool memory resource based on block pool.
98-
template <std::size_t BlockSize, std::size_t BlockPoolExpansion>
99-
class block_pool_resource : public block_resource_base<BlockSize, BlockPoolExpansion>
100-
, public block_collector {
101-
102-
using base_t = block_resource_base<BlockSize, BlockPoolExpansion>;
103-
104-
void recycle(void *p) noexcept override {
105-
base_t::deallocate(p);
106-
}
107-
108-
public:
109-
static block_collector *get() noexcept {
110-
thread_local block_pool_resource instance;
111-
return &instance;
112-
}
113-
114-
template <typename T>
115-
void *allocate(std::size_t bytes, std::size_t alignment = alignof(std::max_align_t)) noexcept {
116-
void *b = base_t::allocate(bytes, alignment);
117-
*static_cast<recycle_t *>(b)
118-
= [](void *b, void *p) noexcept {
119-
std::ignore = destroy(static_cast<T *>(p));
120-
get()->recycle(b);
121-
};
122-
return static_cast<byte *>(b) + regular_head_size;
123-
}
124-
};
40+
namespace detail_new {
12541

126-
/// \brief Different increment levels match different chunk sizes.
127-
/// 512 means that 512 consecutive memory blocks are allocated at a time.
128-
template <std::size_t L>
129-
constexpr std::size_t block_pool_expansion = 0;
130-
131-
template <> constexpr std::size_t block_pool_expansion<0> = 512;
132-
template <> constexpr std::size_t block_pool_expansion<1> = 256;
133-
template <> constexpr std::size_t block_pool_expansion<2> = 128;
134-
template <> constexpr std::size_t block_pool_expansion<3> = 64;
135-
136-
/// \brief Matches the appropriate memory block resource based on the specified type.
137-
template <typename T, std::size_t N = regular_sizeof<T>(), std::size_t L = regular_level(N)>
138-
auto *get_regular_resource() noexcept {
139-
using block_poll_resource_t = block_pool_resource<N, block_pool_expansion<L>>;
140-
return dynamic_cast<block_poll_resource_t *>(block_poll_resource_t::get());
141-
}
142-
template <typename T, std::enable_if_t<std::is_void<T>::value, bool> = true>
143-
auto *get_regular_resource() noexcept {
144-
using block_poll_resource_t = block_pool_resource<0, 0>;
145-
return dynamic_cast<block_poll_resource_t *>(block_poll_resource_t::get());
146-
}
42+
#if defined(LIBIPC_CPP_17)
43+
using recycle_t = void (*)(void *p) noexcept;
44+
#else
45+
using recycle_t = void (*)(void *p);
46+
#endif
14747

148-
namespace detail_new {
48+
static constexpr std::size_t recycler_size = round_up(sizeof(recycle_t), alignof(std::size_t));
49+
static constexpr std::size_t allocated_size = sizeof(std::size_t);
50+
static constexpr std::size_t regular_head_size = round_up(recycler_size + allocated_size, alignof(std::max_align_t));
14951

15052
template <typename T>
15153
struct do_allocate {
152-
template <typename R, typename... A>
153-
static T *apply(R *res, A &&... args) noexcept {
54+
template <typename... A>
55+
static T *apply(A &&... args) noexcept {
56+
void *b = mem::alloc(regular_head_size + sizeof(T));
57+
auto *p = static_cast<byte *>(b) + regular_head_size;
15458
LIBIPC_TRY {
155-
return construct<T>(res->template allocate<T>(sizeof(T), alignof(T)), std::forward<A>(args)...);
59+
T *t = construct<T>(p, std::forward<A>(args)...);
60+
*reinterpret_cast<recycle_t *>(b)
61+
= [](void *p) noexcept {
62+
mem::free(static_cast<byte *>(destroy(static_cast<T *>(p))) - regular_head_size
63+
, regular_head_size + sizeof(T));
64+
};
65+
return t;
15666
} LIBIPC_CATCH(...) {
15767
return nullptr;
15868
}
@@ -161,10 +71,18 @@ struct do_allocate {
16171

16272
template <>
16373
struct do_allocate<void> {
164-
template <typename R>
165-
static void *apply(R *res, std::size_t bytes) noexcept {
74+
static void *apply(std::size_t bytes) noexcept {
16675
if (bytes == 0) return nullptr;
167-
return res->template allocate<void>(bytes);
76+
std::size_t rbz = regular_head_size + bytes;
77+
void *b = mem::alloc(rbz);
78+
*reinterpret_cast<recycle_t *>(b)
79+
= [](void *p) noexcept {
80+
auto *b = static_cast<byte *>(p) - regular_head_size;
81+
mem::free(b, *reinterpret_cast<std::size_t *>(b + recycler_size));
82+
};
83+
auto *z = static_cast<byte *>(b) + recycler_size;
84+
*reinterpret_cast<std::size_t *>(z) = rbz;
85+
return static_cast<byte *>(b) + regular_head_size;
16886
}
16987
};
17088

@@ -174,19 +92,16 @@ struct do_allocate<void> {
17492
/// \note This function is thread-safe.
17593
template <typename T, typename... A>
17694
T *$new(A &&... args) noexcept {
177-
auto *res = get_regular_resource<T>();
178-
if (res == nullptr) return nullptr;
179-
return detail_new::do_allocate<T>::apply(res, std::forward<A>(args)...);
95+
return detail_new::do_allocate<T>::apply(std::forward<A>(args)...);
18096
}
18197

18298
/// \brief Destroys object previously allocated by the `$new` and releases obtained memory area.
18399
/// \note This function is thread-safe. If the pointer type passed in is different from `$new`,
184100
/// additional performance penalties may be incurred.
185101
inline void $delete(void *p) noexcept {
186102
if (p == nullptr) return;
187-
auto *b = reinterpret_cast<byte *>(p) - regular_head_size;
188-
auto *r = reinterpret_cast<recycle_t *>(b);
189-
(*r)(b, p);
103+
auto *r = reinterpret_cast<detail_new::recycle_t *>(static_cast<byte *>(p) - detail_new::regular_head_size);
104+
(*r)(p);
190105
}
191106

192107
/// \brief The destruction policy used by std::unique_ptr.

src/libipc/mem/new.cpp

+121
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
2+
#include "libipc/mem/new.h"
3+
4+
namespace ipc {
5+
namespace mem {
6+
7+
/// \brief Select the incremental level based on the size.
8+
constexpr inline std::size_t regular_level(std::size_t s) noexcept {
9+
return (s <= 128 ) ? 0 :
10+
(s <= 1024 ) ? 1 :
11+
(s <= 8192 ) ? 2 :
12+
(s <= 65536) ? 3 : 4;
13+
}
14+
15+
/// \brief Use block pools to handle memory less than 64K.
16+
template <std::size_t BlockSize, std::size_t BlockPoolExpansion>
17+
class block_resource_base : public block_pool<BlockSize, BlockPoolExpansion>
18+
, public block_collector {
19+
public:
20+
void *allocate(std::size_t /*bytes*/) noexcept override {
21+
return block_pool<BlockSize, BlockPoolExpansion>::allocate();
22+
}
23+
24+
void deallocate(void *p, std::size_t /*bytes*/) noexcept override {
25+
block_pool<BlockSize, BlockPoolExpansion>::deallocate(p);
26+
}
27+
};
28+
29+
/// \brief Use `new`/`delete` to handle memory larger than 64K.
30+
template <>
31+
class block_resource_base<0, 0> : public new_delete_resource
32+
, public block_collector {
33+
public:
34+
void *allocate(std::size_t bytes) noexcept override {
35+
return new_delete_resource::allocate(bytes);
36+
}
37+
38+
void deallocate(void *p, std::size_t bytes) noexcept override {
39+
new_delete_resource::deallocate(p, bytes);
40+
}
41+
};
42+
43+
/// \brief Defines block pool memory resource based on block pool.
44+
template <std::size_t BlockSize, std::size_t BlockPoolExpansion>
45+
class block_pool_resource : public block_resource_base<BlockSize, BlockPoolExpansion> {
46+
public:
47+
static block_collector &get() noexcept {
48+
thread_local block_pool_resource instance;
49+
return instance;
50+
}
51+
};
52+
53+
/// \brief Matches the appropriate memory block resource based on a specified size.
54+
block_collector &get_regular_resource(std::size_t s) noexcept {
55+
std::size_t l = regular_level(s);
56+
switch (l) {
57+
case 0:
58+
switch (round_up<std::size_t>(s, 16)) {
59+
case 16 : return block_pool_resource<16 , 512>::get();
60+
case 32 : return block_pool_resource<32 , 512>::get();
61+
case 48 : return block_pool_resource<48 , 512>::get();
62+
case 64 : return block_pool_resource<64 , 512>::get();
63+
case 80 : return block_pool_resource<80 , 512>::get();
64+
case 96 : return block_pool_resource<96 , 512>::get();
65+
case 112: return block_pool_resource<112, 512>::get();
66+
case 128: return block_pool_resource<128, 512>::get();
67+
default : break;
68+
}
69+
break;
70+
case 1:
71+
switch (round_up<std::size_t>(s, 128)) {
72+
case 256 : return block_pool_resource<256 , 256>::get();
73+
case 384 : return block_pool_resource<384 , 256>::get();
74+
case 512 : return block_pool_resource<512 , 256>::get();
75+
case 640 : return block_pool_resource<640 , 256>::get();
76+
case 768 : return block_pool_resource<768 , 256>::get();
77+
case 896 : return block_pool_resource<896 , 256>::get();
78+
case 1024: return block_pool_resource<1024, 256>::get();
79+
default : break;
80+
}
81+
break;
82+
case 2:
83+
switch (round_up<std::size_t>(s, 1024)) {
84+
case 2048: return block_pool_resource<2048, 128>::get();
85+
case 3072: return block_pool_resource<3072, 128>::get();
86+
case 4096: return block_pool_resource<4096, 128>::get();
87+
case 5120: return block_pool_resource<5120, 128>::get();
88+
case 6144: return block_pool_resource<6144, 128>::get();
89+
case 7168: return block_pool_resource<7168, 128>::get();
90+
case 8192: return block_pool_resource<8192, 128>::get();
91+
default : break;
92+
}
93+
break;
94+
case 3:
95+
switch (round_up<std::size_t>(s, 8192)) {
96+
case 16384: return block_pool_resource<16384, 64>::get();
97+
case 24576: return block_pool_resource<24576, 64>::get();
98+
case 32768: return block_pool_resource<32768, 64>::get();
99+
case 40960: return block_pool_resource<40960, 64>::get();
100+
case 49152: return block_pool_resource<49152, 64>::get();
101+
case 57344: return block_pool_resource<57344, 64>::get();
102+
case 65536: return block_pool_resource<65536, 64>::get();
103+
default : break;
104+
}
105+
break;
106+
default:
107+
break;
108+
}
109+
return block_pool_resource<0, 0>::get();
110+
}
111+
112+
void *alloc(std::size_t bytes) noexcept {
113+
return get_regular_resource(bytes).allocate(bytes);
114+
}
115+
116+
void free(void *p, std::size_t bytes) noexcept {
117+
return get_regular_resource(bytes).deallocate(p, bytes);
118+
}
119+
120+
} // namespace mem
121+
} // namespace ipc

0 commit comments

Comments
 (0)