Skip to content

Commit beba857

Browse files
committed
Add $new
1 parent f989bd7 commit beba857

File tree

2 files changed

+325
-1
lines changed

2 files changed

+325
-1
lines changed

include/libipc/mem/new.h

+156-1
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,173 @@
88
#include <cstddef>
99
#include <algorithm>
1010
#include <type_traits>
11+
#include <limits>
1112

1213
#include "libipc/imp/aligned.h"
1314
#include "libipc/imp/uninitialized.h"
1415
#include "libipc/imp/byte.h"
1516
#include "libipc/imp/detect_plat.h"
16-
1717
#include "libipc/imp/export.h"
18+
#include "libipc/mem/memory_resource.h"
19+
#include "libipc/mem/block_pool.h"
1820

1921
namespace ipc {
2022
namespace mem {
2123

24+
/// \brief Defines the memory block collector interface.
25+
class LIBIPC_EXPORT block_collector {
26+
public:
27+
virtual ~block_collector() noexcept = default;
28+
virtual void recycle(void *p, std::size_t bytes, std::size_t alignment) noexcept = 0;
29+
};
30+
31+
#if defined(LIBIPC_CPP_17)
32+
using get_block_collector_t = block_collector *(*)() noexcept;
33+
#else
34+
using get_block_collector_t = block_collector *(*)();
35+
#endif
36+
37+
static constexpr std::size_t regular_head_size
38+
= round_up(sizeof(get_block_collector_t), alignof(std::max_align_t));
39+
40+
/// \brief Select the incremental level based on the size.
41+
constexpr inline std::size_t regular_level(std::size_t s) noexcept {
42+
return (s <= 128 ) ? 0 :
43+
(s <= 1024 ) ? 1 :
44+
(s <= 8192 ) ? 2 :
45+
(s <= 65536) ? 3 : 4;
46+
}
47+
48+
/// \brief Calculates the appropriate memory block size based on the increment level and size.
49+
constexpr inline std::size_t regular_sizeof_impl(std::size_t l, std::size_t s) noexcept {
50+
return (l == 0) ? round_up<std::size_t>(s, regular_head_size) :
51+
(l == 1) ? round_up<std::size_t>(s, 128 ) :
52+
(l == 2) ? round_up<std::size_t>(s, 1024) :
53+
(l == 3) ? round_up<std::size_t>(s, 8192) : (std::numeric_limits<std::size_t>::max)();
54+
}
55+
56+
/// \brief Calculates the appropriate memory block size based on the size.
57+
constexpr inline std::size_t regular_sizeof_impl(std::size_t s) noexcept {
58+
return regular_sizeof_impl(regular_level(s), s);
59+
}
60+
61+
/// \brief Calculates the appropriate memory block size based on the specific type.
62+
template <typename T>
63+
constexpr inline std::size_t regular_sizeof() noexcept {
64+
return regular_sizeof_impl(regular_head_size + sizeof(T));
65+
}
66+
67+
/// \brief Use block pools to handle memory less than 64K.
68+
template <std::size_t BlockSize, std::size_t BlockPoolExpansion>
69+
class block_resource_base : public block_pool<BlockSize, BlockPoolExpansion> {
70+
public:
71+
void *allocate(std::size_t /*bytes*/, std::size_t /*alignment*/) noexcept {
72+
return block_pool<BlockSize, BlockPoolExpansion>::allocate();
73+
}
74+
75+
void deallocate(void *p, std::size_t /*bytes*/, std::size_t /*alignment*/) noexcept {
76+
block_pool<BlockSize, BlockPoolExpansion>::deallocate(p);
77+
}
78+
};
79+
80+
/// \brief Use `new`/`delete` to handle memory larger than 64K.
81+
template <std::size_t BlockSize>
82+
class block_resource_base<BlockSize, 0> : public new_delete_resource {
83+
public:
84+
void *allocate(std::size_t bytes, std::size_t alignment) noexcept {
85+
return new_delete_resource::allocate(regular_head_size + bytes, alignment);
86+
}
87+
88+
void deallocate(void *p, std::size_t bytes, std::size_t alignment) noexcept {
89+
new_delete_resource::deallocate(p, regular_head_size + bytes, alignment);
90+
}
91+
};
92+
93+
/// \brief Defines block pool memory resource based on block pool.
94+
template <std::size_t BlockSize, std::size_t BlockPoolExpansion>
95+
class block_pool_resource : public block_resource_base<BlockSize, BlockPoolExpansion>
96+
, public block_collector {
97+
98+
using base_t = block_resource_base<BlockSize, BlockPoolExpansion>;
99+
100+
void recycle(void *p, std::size_t bytes, std::size_t alignment) noexcept override {
101+
base_t::deallocate(p, bytes, alignment);
102+
}
103+
104+
public:
105+
static block_collector *get() noexcept {
106+
thread_local block_pool_resource instance;
107+
return &instance;
108+
}
109+
110+
void *allocate(std::size_t bytes, std::size_t alignment = alignof(std::max_align_t)) noexcept {
111+
void *p = base_t::allocate(bytes, alignment);
112+
*static_cast<get_block_collector_t *>(p) = get;
113+
return static_cast<byte *>(p) + regular_head_size;
114+
}
115+
116+
void deallocate(void *p, std::size_t bytes, std::size_t alignment = alignof(std::max_align_t)) noexcept {
117+
p = static_cast<byte *>(p) - regular_head_size;
118+
auto g = *static_cast<get_block_collector_t *>(p);
119+
if (g == get) {
120+
base_t::deallocate(p, bytes, alignment);
121+
return;
122+
}
123+
g()->recycle(p, bytes, alignment);
124+
}
125+
};
126+
127+
/// \brief Different increment levels match different chunk sizes.
128+
/// 512 means that 512 consecutive memory blocks are allocated at a time.
129+
template <std::size_t L>
130+
constexpr std::size_t block_pool_expansion = 0;
131+
132+
template <> constexpr std::size_t block_pool_expansion<0> = 512;
133+
template <> constexpr std::size_t block_pool_expansion<1> = 256;
134+
template <> constexpr std::size_t block_pool_expansion<2> = 128;
135+
template <> constexpr std::size_t block_pool_expansion<3> = 64;
136+
137+
/// \brief Matches the appropriate memory block resource based on the specified type.
138+
template <typename T, std::size_t N = regular_sizeof<T>(), std::size_t L = regular_level(N)>
139+
auto *get_regular_resource() noexcept {
140+
using block_poll_resource_t = block_pool_resource<N, block_pool_expansion<L>>;
141+
return dynamic_cast<block_poll_resource_t *>(block_poll_resource_t::get());
142+
}
143+
144+
/// \brief Creates an object based on the specified type and parameters with block pool resource.
145+
/// \note This function is thread-safe.
146+
template <typename T, typename... A>
147+
T *$new(A &&... args) noexcept {
148+
auto *res = get_regular_resource<T>();
149+
if (res == nullptr) return nullptr;
150+
return construct<T>(res->allocate(sizeof(T), alignof(T)), std::forward<A>(args)...);
151+
}
152+
153+
/// \brief Destroys object previously allocated by the `$new` and releases obtained memory area.
154+
/// \note This function is thread-safe. If the pointer type passed in is different from `$new`,
155+
/// additional performance penalties may be incurred.
156+
template <typename T>
157+
void $delete(T *p) noexcept {
158+
if (p == nullptr) return;
159+
destroy(p);
160+
auto *res = get_regular_resource<T>();
161+
if (res == nullptr) return;
162+
#if (LIBIPC_CC_MSVC > LIBIPC_CC_MSVC_2015)
163+
res->deallocate(p, sizeof(T), alignof(T));
164+
#else
165+
// `alignof` of vs2015 requires that type must be able to be instantiated.
166+
res->deallocate(p, sizeof(T));
167+
#endif
168+
}
22169

170+
/// \brief The destruction policy used by std::unique_ptr.
171+
/// \see https://en.cppreference.com/w/cpp/memory/default_delete
172+
struct deleter {
173+
template <typename T>
174+
void operator()(T *p) const noexcept {
175+
$delete(p);
176+
}
177+
};
23178

24179
} // namespace mem
25180
} // namespace ipc

test/mem/test_mem_new.cpp

+169
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
2+
#include <limits>
3+
#include <array>
4+
#include <cstring>
5+
#include <cstddef>
6+
#include <thread>
7+
8+
#include "test.h"
9+
10+
#include "libipc/mem/new.h"
11+
12+
TEST(new, regular_sizeof) {
13+
ASSERT_EQ(ipc::mem::regular_sizeof<std::int8_t >(), ipc::mem::regular_head_size + alignof(std::max_align_t));
14+
ASSERT_EQ(ipc::mem::regular_sizeof<std::int16_t>(), ipc::mem::regular_head_size + alignof(std::max_align_t));
15+
ASSERT_EQ(ipc::mem::regular_sizeof<std::int32_t>(), ipc::mem::regular_head_size + alignof(std::max_align_t));
16+
ASSERT_EQ(ipc::mem::regular_sizeof<std::int64_t>(), ipc::mem::regular_head_size + alignof(std::max_align_t));
17+
18+
ASSERT_EQ((ipc::mem::regular_sizeof<std::array<char, 10 >>()), ipc::round_up<std::size_t>(ipc::mem::regular_head_size + 10 , alignof(std::max_align_t)));
19+
ASSERT_EQ((ipc::mem::regular_sizeof<std::array<char, 100 >>()), ipc::round_up<std::size_t>(ipc::mem::regular_head_size + 100 , alignof(std::max_align_t)));
20+
ASSERT_EQ((ipc::mem::regular_sizeof<std::array<char, 1000 >>()), ipc::round_up<std::size_t>(ipc::mem::regular_head_size + 1000 , 128));
21+
ASSERT_EQ((ipc::mem::regular_sizeof<std::array<char, 10000 >>()), ipc::round_up<std::size_t>(ipc::mem::regular_head_size + 10000, 8192));
22+
ASSERT_EQ((ipc::mem::regular_sizeof<std::array<char, 100000>>()), (std::numeric_limits<std::size_t>::max)());
23+
}
24+
25+
TEST(new, new) {
26+
auto p = ipc::mem::$new<int>();
27+
ASSERT_NE(p, nullptr);
28+
*p = -1;
29+
ASSERT_EQ(*p, -1);
30+
ipc::mem::$delete(p);
31+
}
32+
33+
TEST(new, new_value) {
34+
auto p = ipc::mem::$new<int>((std::numeric_limits<int>::max)());
35+
ASSERT_NE(p, nullptr);
36+
ASSERT_EQ(*p, (std::numeric_limits<int>::max)());
37+
ipc::mem::$delete(p);
38+
}
39+
40+
namespace {
41+
42+
template <std::size_t Pts, std::size_t N>
43+
void test_new$array() {
44+
std::array<void *, Pts> pts;
45+
using T = std::array<char, N>;
46+
for (int i = 0; i < (int)pts.size(); ++i) {
47+
auto p = ipc::mem::$new<T>();
48+
pts[i] = p;
49+
std::memset(p, i, sizeof(T));
50+
}
51+
for (int i = 0; i < (int)pts.size(); ++i) {
52+
T tmp;
53+
std::memset(&tmp, i, sizeof(T));
54+
ASSERT_EQ(std::memcmp(pts[i], &tmp, sizeof(T)), 0);
55+
ipc::mem::$delete(static_cast<T *>(pts[i]));
56+
}
57+
}
58+
59+
} // namespace
60+
61+
TEST(new, new_array) {
62+
test_new$array<1000, 10>();
63+
test_new$array<1000, 100>();
64+
test_new$array<1000, 1000>();
65+
test_new$array<1000, 10000>();
66+
test_new$array<1000, 100000>();
67+
// test_new$array<1000, 1000000>();
68+
}
69+
70+
namespace {
71+
72+
int construct_count__ = 0;
73+
74+
class Base {
75+
public:
76+
virtual ~Base() = default;
77+
virtual int get() const = 0;
78+
};
79+
80+
class Derived : public Base {
81+
public:
82+
Derived(int value) : value_(value) {
83+
construct_count__ = value_;
84+
}
85+
86+
~Derived() override {
87+
construct_count__ = 0;
88+
}
89+
90+
int get() const override { return value_; }
91+
92+
private:
93+
int value_;
94+
};
95+
96+
class Derived64K : public Derived {
97+
public:
98+
using Derived::Derived;
99+
100+
private:
101+
std::array<char, 65536> padding_;
102+
};
103+
104+
} // namespace
105+
106+
TEST(new, delete_poly) {
107+
Base *p = ipc::mem::$new<Derived>(-1);
108+
ASSERT_NE(p, nullptr);
109+
ASSERT_EQ(p->get(), -1);
110+
ASSERT_EQ(construct_count__, -1);
111+
ipc::mem::$delete(p);
112+
ASSERT_EQ(construct_count__, 0);
113+
114+
ASSERT_EQ(p, ipc::mem::$new<Derived>((std::numeric_limits<int>::max)()));
115+
ASSERT_EQ(p->get(), (std::numeric_limits<int>::max)());
116+
ASSERT_EQ(construct_count__, (std::numeric_limits<int>::max)());
117+
ipc::mem::$delete(p);
118+
ASSERT_EQ(construct_count__, 0);
119+
}
120+
121+
TEST(new, delete_poly64k) {
122+
Base *p = ipc::mem::$new<Derived64K>(-1);
123+
ASSERT_NE(p, nullptr);
124+
ASSERT_EQ(p->get(), -1);
125+
ASSERT_EQ(construct_count__, -1);
126+
ipc::mem::$delete(p);
127+
ASSERT_EQ(construct_count__, 0);
128+
129+
Base *q = ipc::mem::$new<Derived64K>((std::numeric_limits<int>::max)());
130+
ASSERT_EQ(q->get(), (std::numeric_limits<int>::max)());
131+
ASSERT_EQ(construct_count__, (std::numeric_limits<int>::max)());
132+
ipc::mem::$delete(q);
133+
ASSERT_EQ(construct_count__, 0);
134+
}
135+
136+
TEST(new, delete_null) {
137+
Base *p = nullptr;
138+
ipc::mem::$delete(p);
139+
SUCCEED();
140+
}
141+
142+
TEST(new, multi_thread) {
143+
std::array<std::thread, 16> threads;
144+
for (auto &t : threads) {
145+
t = std::thread([] {
146+
for (int i = 0; i < 10000; ++i) {
147+
auto p = ipc::mem::$new<int>();
148+
*p = i;
149+
ipc::mem::$delete(p);
150+
}
151+
std::array<void *, 10000> pts;
152+
for (int i = 0; i < 10000; ++i) {
153+
auto p = ipc::mem::$new<std::array<char, 10>>();
154+
pts[i] = p;
155+
std::memset(p, i, sizeof(std::array<char, 10>));
156+
}
157+
for (int i = 0; i < 10000; ++i) {
158+
std::array<char, 10> tmp;
159+
std::memset(&tmp, i, sizeof(std::array<char, 10>));
160+
ASSERT_EQ(std::memcmp(pts[i], &tmp, sizeof(std::array<char, 10>)), 0);
161+
ipc::mem::$delete(static_cast<std::array<char, 10> *>(pts[i]));
162+
}
163+
});
164+
}
165+
for (auto &t : threads) {
166+
t.join();
167+
}
168+
SUCCEED();
169+
}

0 commit comments

Comments
 (0)