Skip to content

Commit 21db9f3

Browse files
committed
Optimize memory_resource & add monotonic_buffer_resource
1 parent ad21fac commit 21db9f3

File tree

7 files changed

+375
-58
lines changed

7 files changed

+375
-58
lines changed

include/libipc/mem/allocator.h

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,37 @@
1515
#include "libipc/imp/export.h"
1616
#include "libipc/imp/uninitialized.h"
1717
#include "libipc/imp/byte.h"
18-
#include "libipc/mem/memory_resource.h"
1918

2019
namespace ipc {
2120
namespace mem {
2221

22+
/// \brief Helper trait for memory resource.
23+
24+
template <typename T, typename = void>
25+
struct has_allocate : std::false_type {};
26+
27+
template <typename T>
28+
struct has_allocate<T,
29+
typename std::enable_if<std::is_convertible<
30+
decltype(std::declval<T &>().allocate(std::declval<std::size_t>(),
31+
std::declval<std::size_t>())), void *
32+
>::value>::type> : std::true_type {};
33+
34+
template <typename T, typename = void>
35+
struct has_deallocate : std::false_type {};
36+
37+
template <typename T>
38+
struct has_deallocate<T,
39+
decltype(std::declval<T &>().deallocate(std::declval<void *>(),
40+
std::declval<std::size_t>(),
41+
std::declval<std::size_t>()))
42+
> : std::true_type {};
43+
44+
template <typename T>
45+
using is_memory_resource =
46+
std::enable_if_t<has_allocate <T>::value &&
47+
has_deallocate<T>::value, bool>;
48+
2349
/**
2450
* \brief An allocator which exhibits different allocation behavior
2551
* depending upon the memory resource from which it is constructed.
@@ -57,7 +83,7 @@ class LIBIPC_EXPORT allocator {
5783
holder_mr(MR *p_mr) noexcept
5884
: res_(p_mr) {}
5985

60-
// [MSVC] error C2259: 'pmr::allocator::holder_mr<void *,bool>': cannot instantiate abstract class.
86+
// [MSVC] error C2259: 'allocator::holder_mr<void *,bool>': cannot instantiate abstract class.
6187
void *alloc(std::size_t s, std::size_t a) const override { return nullptr; }
6288
void dealloc(void *p, std::size_t s, std::size_t a) const override {}
6389
};
@@ -67,7 +93,7 @@ class LIBIPC_EXPORT allocator {
6793
* \tparam MR memory resource type
6894
*/
6995
template <typename MR>
70-
class holder_mr<MR, verify_memory_resource<MR>> : public holder_mr<MR, void> {
96+
class holder_mr<MR, is_memory_resource<MR>> : public holder_mr<MR, void> {
7197
using base_t = holder_mr<MR, void>;
7298

7399
public:
@@ -89,6 +115,8 @@ class LIBIPC_EXPORT allocator {
89115
holder_mr_base & get_holder() noexcept;
90116
holder_mr_base const &get_holder() const noexcept;
91117

118+
void init_default_resource() noexcept;
119+
92120
public:
93121
/// \brief Constructs an `allocator` using the return value of
94122
/// `new_delete_resource::get()` as the underlying memory resource.
@@ -103,13 +131,13 @@ class LIBIPC_EXPORT allocator {
103131

104132
/// \brief Constructs a allocator from a memory resource pointer.
105133
/// \note The lifetime of the pointer must be longer than that of allocator.
106-
template <typename T, verify_memory_resource<T> = true>
134+
template <typename T, is_memory_resource<T> = true>
107135
allocator(T *p_mr) noexcept {
108136
if (p_mr == nullptr) {
109-
ipc::construct<holder_mr<new_delete_resource>>(holder_.data(), new_delete_resource::get());
137+
init_default_resource();
110138
return;
111139
}
112-
ipc::construct<holder_mr<T>>(holder_.data(), p_mr);
140+
std::ignore = ipc::construct<holder_mr<T>>(holder_.data(), p_mr);
113141
}
114142

115143
void swap(allocator &other) noexcept;

include/libipc/mem/memory_resource.h

Lines changed: 46 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,21 @@
11
/**
22
* \file libipc/memory_resource.h
33
* \author mutouyun ([email protected])
4-
* \brief Implement memory allocation strategies that can be used by pmr::allocator.
4+
* \brief Implement memory allocation strategies that can be used by ipc::mem::allocator.
55
*/
66
#pragma once
77

88
#include <type_traits>
99
#include <cstddef> // std::size_t, std::max_align_t
1010

1111
#include "libipc/imp/export.h"
12-
#include "libipc/def.h"
12+
#include "libipc/imp/span.h"
13+
#include "libipc/imp/byte.h"
14+
#include "libipc/mem/allocator.h"
1315

1416
namespace ipc {
1517
namespace mem {
1618

17-
/// \brief Helper trait for memory resource.
18-
19-
template <typename T, typename = void>
20-
struct has_allocate : std::false_type {};
21-
22-
template <typename T>
23-
struct has_allocate<T,
24-
typename std::enable_if<std::is_convertible<
25-
decltype(std::declval<T &>().allocate(std::declval<std::size_t>(),
26-
std::declval<std::size_t>())), void *
27-
>::value>::type> : std::true_type {};
28-
29-
template <typename T, typename = void>
30-
struct has_deallocate : std::false_type {};
31-
32-
template <typename T>
33-
struct has_deallocate<T,
34-
decltype(std::declval<T &>().deallocate(std::declval<void *>(),
35-
std::declval<std::size_t>(),
36-
std::declval<std::size_t>()))
37-
> : std::true_type {};
38-
39-
template <typename T>
40-
using verify_memory_resource =
41-
std::enable_if_t<has_allocate <T>::value &&
42-
has_deallocate<T>::value, bool>;
43-
4419
/**
4520
* \class LIBIPC_EXPORT new_delete_resource
4621
* \brief A memory resource that uses the
@@ -62,5 +37,47 @@ class LIBIPC_EXPORT new_delete_resource {
6237
void deallocate(void *p, std::size_t bytes, std::size_t alignment = alignof(std::max_align_t)) noexcept;
6338
};
6439

40+
/**
41+
* \class LIBIPC_EXPORT monotonic_buffer_resource
42+
* \brief A special-purpose memory resource class
43+
* that releases the allocated memory only when the resource is destroyed.
44+
* \see https://en.cppreference.com/w/cpp/memory/monotonic_buffer_resource
45+
*/
46+
class LIBIPC_EXPORT monotonic_buffer_resource {
47+
48+
allocator upstream_;
49+
50+
struct node {
51+
node *next;
52+
std::size_t size;
53+
} *free_list_;
54+
55+
ipc::byte * head_;
56+
ipc::byte * tail_;
57+
std::size_t next_size_;
58+
59+
ipc::byte * const initial_buffer_;
60+
std::size_t const initial_size_;
61+
62+
public:
63+
monotonic_buffer_resource() noexcept;
64+
explicit monotonic_buffer_resource(allocator upstream) noexcept;
65+
explicit monotonic_buffer_resource(std::size_t initial_size) noexcept;
66+
monotonic_buffer_resource(std::size_t initial_size, allocator upstream) noexcept;
67+
monotonic_buffer_resource(ipc::span<ipc::byte> buffer) noexcept;
68+
monotonic_buffer_resource(ipc::span<ipc::byte> buffer, allocator upstream) noexcept;
69+
70+
~monotonic_buffer_resource() noexcept;
71+
72+
monotonic_buffer_resource(monotonic_buffer_resource const &) = delete;
73+
monotonic_buffer_resource &operator=(monotonic_buffer_resource const &) = delete;
74+
75+
allocator upstream_resource() const noexcept;
76+
void release() noexcept;
77+
78+
void *allocate(std::size_t bytes, std::size_t alignment = alignof(std::max_align_t)) noexcept;
79+
void deallocate(void *p, std::size_t bytes, std::size_t alignment = alignof(std::max_align_t)) noexcept;
80+
};
81+
6582
} // namespace mem
6683
} // namespace ipc

src/libipc/mem/allocator.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
#include "libipc/imp/log.h"
55
#include "libipc/mem/allocator.h"
6+
#include "libipc/mem/memory_resource.h"
67

78
namespace ipc {
89
namespace mem {
@@ -15,6 +16,10 @@ allocator::holder_mr_base const &allocator::get_holder() const noexcept {
1516
return *reinterpret_cast<holder_mr_base const *>(holder_.data());
1617
}
1718

19+
void allocator::init_default_resource() noexcept {
20+
std::ignore = ipc::construct<holder_mr<new_delete_resource>>(holder_.data(), new_delete_resource::get());
21+
}
22+
1823
allocator::allocator() noexcept
1924
: allocator(new_delete_resource::get()) {}
2025

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
2+
#include <utility>
3+
#include <memory>
4+
#include <algorithm>
5+
6+
#include "libipc/imp/log.h"
7+
#include "libipc/imp/aligned.h"
8+
#include "libipc/imp/detect_plat.h"
9+
#include "libipc/mem/memory_resource.h"
10+
11+
namespace ipc {
12+
namespace mem {
13+
namespace {
14+
15+
template <typename Node>
16+
Node *make_node(allocator const &upstream, std::size_t initial_size, std::size_t alignment) noexcept {
17+
LIBIPC_LOG();
18+
auto sz = ipc::round_up(sizeof(Node), alignment) + initial_size;
19+
LIBIPC_TRY {
20+
auto *node = static_cast<Node *>(upstream.allocate(sz));
21+
if (node == nullptr) {
22+
log.error("failed: allocate memory for `monotonic_buffer_resource`'s node.",
23+
" bytes = ", initial_size, ", alignment = ", alignment);
24+
return nullptr;
25+
}
26+
node->next = nullptr;
27+
node->size = sz;
28+
return node;
29+
} LIBIPC_CATCH(...) {
30+
log.error("failed: allocate memory for `monotonic_buffer_resource`'s node.",
31+
" bytes = ", initial_size, ", alignment = ", alignment,
32+
"\n\texception: ", ipc::log::exception_string(std::current_exception()));
33+
return nullptr;
34+
}
35+
}
36+
37+
std::size_t next_buffer_size(std::size_t size) noexcept {
38+
return size * 3 / 2;
39+
}
40+
41+
} // namespace
42+
43+
monotonic_buffer_resource::monotonic_buffer_resource() noexcept
44+
: monotonic_buffer_resource(allocator{}) {}
45+
46+
monotonic_buffer_resource::monotonic_buffer_resource(allocator upstream) noexcept
47+
: monotonic_buffer_resource(0, std::move(upstream)) {}
48+
49+
monotonic_buffer_resource::monotonic_buffer_resource(std::size_t initial_size) noexcept
50+
: monotonic_buffer_resource(initial_size, allocator{}) {}
51+
52+
monotonic_buffer_resource::monotonic_buffer_resource(std::size_t initial_size, allocator upstream) noexcept
53+
: upstream_ (std::move(upstream))
54+
, free_list_ (nullptr)
55+
, head_ (nullptr)
56+
, tail_ (nullptr)
57+
, next_size_ (initial_size)
58+
, initial_buffer_(nullptr)
59+
, initial_size_ (initial_size) {}
60+
61+
monotonic_buffer_resource::monotonic_buffer_resource(ipc::span<ipc::byte> buffer) noexcept
62+
: monotonic_buffer_resource(buffer, allocator{}) {}
63+
64+
monotonic_buffer_resource::monotonic_buffer_resource(ipc::span<ipc::byte> buffer, allocator upstream) noexcept
65+
: upstream_ (std::move(upstream))
66+
, free_list_ (nullptr)
67+
, head_ (buffer.begin())
68+
, tail_ (buffer.end())
69+
, next_size_ (next_buffer_size(buffer.size()))
70+
, initial_buffer_(buffer.begin())
71+
, initial_size_ (buffer.size()) {}
72+
73+
monotonic_buffer_resource::~monotonic_buffer_resource() noexcept {
74+
release();
75+
}
76+
77+
allocator monotonic_buffer_resource::upstream_resource() const noexcept {
78+
return upstream_;
79+
}
80+
81+
void monotonic_buffer_resource::release() noexcept {
82+
LIBIPC_LOG();
83+
LIBIPC_TRY {
84+
while (free_list_ != nullptr) {
85+
auto *next = free_list_->next;
86+
upstream_.deallocate(free_list_, free_list_->size);
87+
free_list_ = next;
88+
}
89+
} LIBIPC_CATCH(...) {
90+
log.error("failed: deallocate memory for `monotonic_buffer_resource`.",
91+
"\n\texception: ", ipc::log::exception_string(std::current_exception()));
92+
}
93+
// reset to initial state at contruction
94+
if ((head_ = initial_buffer_) != nullptr) {
95+
tail_ = head_ + initial_size_;
96+
next_size_ = next_buffer_size(initial_size_);
97+
} else {
98+
tail_ = nullptr;
99+
next_size_ = initial_size_;
100+
}
101+
}
102+
103+
void *monotonic_buffer_resource::allocate(std::size_t bytes, std::size_t alignment) noexcept {
104+
LIBIPC_LOG();
105+
if (bytes == 0) {
106+
log.error("failed: allocate bytes = 0.");
107+
return nullptr;
108+
}
109+
void *p = head_;
110+
auto s = static_cast<std::size_t>(tail_ - head_);
111+
if (std::align(alignment, bytes, p, s) == nullptr) {
112+
next_size_ = (std::max)(next_size_, bytes);
113+
auto *node = make_node<monotonic_buffer_resource::node>(upstream_, next_size_, alignment);
114+
if (node == nullptr) return nullptr;
115+
node->next = free_list_;
116+
free_list_ = node;
117+
next_size_ = next_buffer_size(next_size_);
118+
// try again
119+
s = node->size - sizeof(monotonic_buffer_resource::node);
120+
p = std::align(alignment, bytes, (p = node + 1), s);
121+
if (p == nullptr) {
122+
log.error("failed: allocate memory for `monotonic_buffer_resource`.",
123+
" bytes = ", bytes, ", alignment = ", alignment);
124+
return nullptr;
125+
}
126+
tail_ = static_cast<ipc::byte *>(p) + s;
127+
}
128+
head_ = static_cast<ipc::byte *>(p) + bytes;
129+
return p;
130+
}
131+
132+
void monotonic_buffer_resource::deallocate(void *p, std::size_t bytes, std::size_t alignment) noexcept {
133+
static_cast<void>(p);
134+
static_cast<void>(bytes);
135+
static_cast<void>(alignment);
136+
// Do nothing.
137+
}
138+
139+
} // namespace mem
140+
} // namespace ipc

test/mem/test_mem_allocator.cpp

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,12 @@
44

55
#include "test.h"
66

7+
#if defined(LIBIPC_CPP_17) && defined(__cpp_lib_memory_resource)
8+
# include <memory_resource>
9+
#endif
10+
711
#include "libipc/mem/allocator.h"
12+
#include "libipc/mem/memory_resource.h"
813

914
TEST(allocator, construct) {
1015
ipc::mem::allocator alc;
@@ -32,6 +37,26 @@ class dummy_resource {
3237

3338
} // namespace
3439

40+
TEST(allocator, memory_resource_traits) {
41+
EXPECT_FALSE(ipc::mem::has_allocate<void>::value);
42+
EXPECT_FALSE(ipc::mem::has_allocate<int>::value);
43+
EXPECT_FALSE(ipc::mem::has_allocate<std::vector<int>>::value);
44+
EXPECT_FALSE(ipc::mem::has_allocate<std::allocator<int>>::value);
45+
#if defined(LIBIMP_CPP_17) && defined(__cpp_lib_memory_resource)
46+
EXPECT_TRUE (ipc::mem::has_allocate<std::ipc::mem::memory_resource>::value);
47+
EXPECT_TRUE (ipc::mem::has_allocate<std::ipc::mem::polymorphic_allocator<int>>::value);
48+
#endif
49+
50+
EXPECT_FALSE(ipc::mem::has_deallocate<void>::value);
51+
EXPECT_FALSE(ipc::mem::has_deallocate<int>::value);
52+
EXPECT_FALSE(ipc::mem::has_deallocate<std::vector<int>>::value);
53+
EXPECT_FALSE(ipc::mem::has_deallocate<std::allocator<int>>::value);
54+
#if defined(LIBIMP_CPP_17) && defined(__cpp_lib_memory_resource)
55+
EXPECT_TRUE (ipc::mem::has_deallocate<std::ipc::mem::memory_resource>::value);
56+
EXPECT_FALSE(ipc::mem::has_deallocate<std::ipc::mem::polymorphic_allocator<int>>::value);
57+
#endif
58+
}
59+
3560
TEST(allocator, construct_copy_move) {
3661
ipc::mem::new_delete_resource mem_res;
3762
dummy_resource dummy_res;

0 commit comments

Comments
 (0)