Skip to content

Commit db8f4d6

Browse files
committed
util: added std::pmr resource pool
1 parent 5dedaac commit db8f4d6

5 files changed

Lines changed: 599 additions & 0 deletions

File tree

api/util/alloc_pmr.hpp

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
// This file is a part of the IncludeOS unikernel - www.includeos.org
2+
//
3+
// Copyright 2018 IncludeOS AS, Oslo, Norway
4+
//
5+
// Licensed under the Apache License, Version 2.0 (the "License");
6+
// you may not use this file except in compliance with the License.
7+
// You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing, software
12+
// distributed under the License is distributed on an "AS IS" BASIS,
13+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
// See the License for the specific language governing permissions and
15+
// limitations under the License.
16+
17+
#ifndef UTIL_ALLOC_PMR
18+
#define UTIL_ALLOC_PMR
19+
20+
#if __has_include(<experimental/memory_resource>)
21+
#include <experimental/memory_resource>
22+
#include <experimental/vector>
23+
namespace std {
24+
namespace pmr = std::experimental::pmr;
25+
}
26+
#else
27+
#include <memory_resource>
28+
#include <vector> // For pmr::vector
29+
#endif
30+
31+
32+
#include <delegate>
33+
#include <malloc.h>
34+
35+
namespace os::mem::detail {
36+
class Pmr_pool;
37+
using Pool_ptr = std::shared_ptr<Pmr_pool>;
38+
}
39+
40+
namespace os::mem {
41+
42+
class Pmr_resource;
43+
44+
class Pmr_pool {
45+
public:
46+
static constexpr size_t default_max_resources = 64;
47+
using Resource = Pmr_resource;
48+
using Resource_ptr = std::unique_ptr<Pmr_resource, delegate<void(Resource*)>>;
49+
50+
inline
51+
Pmr_pool(size_t capacity,
52+
size_t cap_suballoc = 0,
53+
size_t max_rescount = default_max_resources);
54+
inline Resource_ptr get_resource();
55+
inline void return_resource(Resource* res);
56+
inline std::size_t resource_capacity();
57+
inline std::size_t resource_count();
58+
inline std::size_t total_capacity();
59+
inline void set_resource_capacity(std::size_t);
60+
inline void set_total_capacity(std::size_t);
61+
inline std::size_t bytes_used();
62+
inline std::size_t bytes_free();
63+
inline bool empty();
64+
private:
65+
detail::Pool_ptr impl;
66+
};
67+
68+
69+
class Pmr_resource : public std::pmr::memory_resource {
70+
public:
71+
using Pool_ptr = detail::Pool_ptr;
72+
inline Pmr_resource(Pool_ptr p);
73+
inline Pool_ptr pool();
74+
inline void* do_allocate(std::size_t size, std::size_t align) override;
75+
inline void do_deallocate (void* ptr, size_t, size_t) override;
76+
inline bool do_is_equal(const std::pmr::memory_resource&) const noexcept override;
77+
inline std::size_t capacity();
78+
inline std::size_t bytes_free();
79+
inline std::size_t bytes_used();
80+
inline std::size_t allocations();
81+
inline std::size_t deallocations();
82+
inline bool empty();
83+
private:
84+
Pool_ptr pool_;
85+
std::size_t used = 0;
86+
std::size_t allocs = 0;
87+
std::size_t deallocs = 0;
88+
};
89+
90+
struct Default_pmr : public std::pmr::memory_resource {
91+
void* do_allocate(std::size_t size, std::size_t align) override {
92+
return memalign(align, size);
93+
}
94+
95+
void do_deallocate (void* ptr, size_t, size_t) override {
96+
std::free(ptr);
97+
}
98+
99+
bool do_is_equal (const std::pmr::memory_resource& other) const noexcept override {
100+
if (const auto* underlying = dynamic_cast<const Default_pmr*>(&other))
101+
return true;
102+
return false;
103+
}
104+
};
105+
106+
}
107+
108+
#include <util/detail/alloc_pmr.hpp>
109+
110+
#endif

api/util/detail/alloc_pmr.hpp

Lines changed: 259 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,259 @@
1+
// This file is a part of the IncludeOS unikernel - www.includeos.org
2+
//
3+
// Copyright 2018 IncludeOS AS, Oslo, Norway
4+
//
5+
// Licensed under the Apache License, Version 2.0 (the "License");
6+
// you may not use this file except in compliance with the License.
7+
// You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing, software
12+
// distributed under the License is distributed on an "AS IS" BASIS,
13+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
// See the License for the specific language governing permissions and
15+
// limitations under the License.
16+
17+
#ifndef UTIL_DETAIL_ALLOC_PMR
18+
#define UTIL_DETAIL_ALLOC_PMR
19+
20+
#include <common>
21+
#include <deque>
22+
#include <delegate>
23+
#include <util/units.hpp>
24+
25+
namespace os::mem::detail {
26+
27+
class Pmr_pool : public std::enable_shared_from_this<Pmr_pool>, public std::pmr::memory_resource {
28+
public:
29+
using Resource = os::mem::Pmr_pool::Resource;
30+
using Resource_ptr = os::mem::Pmr_pool::Resource_ptr;
31+
Pmr_pool(std::size_t total_max, std::size_t suballoc_max, std::size_t max_allocs)
32+
: cap_total_{total_max}, cap_suballoc_{suballoc_max}, max_resources_{max_allocs} {}
33+
34+
void* do_allocate(size_t size, size_t align) override {
35+
if (UNLIKELY(size + allocated_ > cap_total_)) {
36+
throw std::bad_alloc();
37+
}
38+
39+
// Adapt to memalign's minimum size- and alignemnt requiremnets
40+
if (align < sizeof(void*))
41+
align = sizeof(void*);
42+
43+
if (size < sizeof(void*))
44+
size = sizeof(void*);
45+
46+
void* buf = memalign(align, size);
47+
48+
if (buf == nullptr) {
49+
throw std::bad_alloc();
50+
}
51+
52+
allocated_ += size;
53+
return buf;
54+
}
55+
56+
void do_deallocate (void* ptr, size_t size, size_t) override {
57+
58+
// Adapt to memalign
59+
if (size < sizeof(void*))
60+
size = sizeof(void*);
61+
62+
free(ptr);
63+
allocated_ -= size;
64+
}
65+
66+
bool do_is_equal(const std::pmr::memory_resource&) const noexcept override {
67+
return true;
68+
}
69+
70+
Resource_ptr resource_from_raw(Resource* raw) {
71+
Resource_ptr res_ptr(raw, [this](auto* res) {
72+
Expects(res->pool().get() == this);
73+
this->return_resource(res);
74+
});
75+
76+
return res_ptr;
77+
}
78+
79+
Resource_ptr get_resource() {
80+
81+
if (! free_resources_.empty()) {
82+
auto res = std::move(free_resources_.back());
83+
free_resources_.pop_back();
84+
return res;
85+
}
86+
87+
if (used_resources_ >= max_resources_)
88+
return nullptr;
89+
90+
auto res = resource_from_raw(new Pmr_resource(shared_ptr()));
91+
92+
used_resources_++;
93+
94+
Ensures(res != nullptr);
95+
Ensures(used_resources_ <= max_resources_);
96+
return res;
97+
}
98+
99+
void return_resource(Resource* raw) {
100+
auto res_ptr = resource_from_raw(raw);
101+
used_resources_--;
102+
free_resources_.emplace_back(std::move(res_ptr));
103+
}
104+
105+
std::shared_ptr<Pmr_pool> shared_ptr() {
106+
return shared_from_this();
107+
}
108+
109+
std::size_t resource_count() {
110+
return used_resources_ + free_resources_.size();
111+
}
112+
113+
std::size_t free_resources() {
114+
return free_resources_.size();
115+
}
116+
117+
std::size_t used_resources() {
118+
return used_resources_;
119+
}
120+
121+
std::size_t total_capacity() {
122+
return cap_total_;
123+
}
124+
125+
void set_resource_capacity(std::size_t sz) {
126+
cap_suballoc_ = sz;
127+
}
128+
129+
void set_total_capacity(std::size_t sz) {
130+
cap_total_ = sz;
131+
}
132+
133+
std::size_t resource_capacity() {
134+
if (cap_suballoc_ == 0) {
135+
if (used_resources_ == 0)
136+
return cap_total_;
137+
return cap_total_ / used_resources_;
138+
}
139+
return cap_suballoc_;
140+
}
141+
142+
std::size_t bytes_booked() {
143+
return cap_suballoc_ * used_resources_;
144+
}
145+
146+
std::size_t bytes_bookable() {
147+
auto booked = bytes_booked();
148+
if (booked > cap_total_)
149+
return 0;
150+
151+
return cap_total_ - booked;
152+
}
153+
154+
155+
std::size_t bytes_used() {
156+
return allocated_;
157+
}
158+
159+
bool empty() {
160+
return allocated_ >= cap_total_;
161+
}
162+
163+
std::size_t bytes_free() {
164+
auto allocd = bytes_used();
165+
if (allocd > cap_total_)
166+
return 0;
167+
return cap_total_ - allocd;
168+
}
169+
170+
// NOTE: This can cause leaks or other chaos if you're not sure what you're doing
171+
void clear_free_resources() {
172+
for (auto& res : free_resources_) {
173+
*res = Resource(shared_ptr());
174+
}
175+
}
176+
177+
private:
178+
std::size_t allocated_ = 0;
179+
std::size_t cap_total_ = 0;
180+
std::size_t cap_suballoc_ = 0;
181+
std::size_t max_resources_ = 0;
182+
std::size_t used_resources_ = 0;
183+
std::deque<Resource_ptr> free_resources_{};
184+
};
185+
186+
} // os::mem::detail
187+
188+
189+
namespace os::mem {
190+
191+
//
192+
// Pmr_pool implementatino (PIMPL wrapper)
193+
//
194+
std::size_t Pmr_pool::total_capacity() { return impl->total_capacity(); }
195+
std::size_t Pmr_pool::resource_capacity() { return impl->resource_capacity(); }
196+
std::size_t Pmr_pool::bytes_free() { return impl->bytes_free(); }
197+
std::size_t Pmr_pool::bytes_used() { return impl->bytes_used(); }
198+
void Pmr_pool::set_resource_capacity(std::size_t s) { impl->set_resource_capacity(s); }
199+
void Pmr_pool::set_total_capacity(std::size_t s) { impl->set_total_capacity(s); };
200+
201+
Pmr_pool::Pmr_pool(size_t sz, size_t sz_sub, size_t max_allocs)
202+
: impl{std::make_shared<detail::Pmr_pool>(sz, sz_sub, max_allocs)}{}
203+
Pmr_pool::Resource_ptr Pmr_pool::get_resource() { return impl->get_resource(); }
204+
std::size_t Pmr_pool::resource_count() { return impl->resource_count(); }
205+
void Pmr_pool::return_resource(Resource* res) { impl->return_resource(res); }
206+
bool Pmr_pool::empty() { return impl->empty(); }
207+
208+
//
209+
// Pmr_resource implementation
210+
//
211+
Pmr_resource::Pmr_resource(Pool_ptr p) : pool_{p} {}
212+
std::size_t Pmr_resource::capacity() { return pool_->resource_capacity(); }
213+
std::size_t Pmr_resource::bytes_free() {
214+
auto cap = capacity();
215+
if (used > capacity())
216+
return 0;
217+
return cap - used;
218+
}
219+
std::size_t Pmr_resource::bytes_used() {
220+
return used;
221+
}
222+
223+
Pmr_resource::Pool_ptr Pmr_resource::pool() {
224+
return pool_;
225+
}
226+
227+
void* Pmr_resource::do_allocate(std::size_t size, std::size_t align) {
228+
auto cap = capacity();
229+
if (UNLIKELY(size + used > cap)) {
230+
throw std::bad_alloc();
231+
}
232+
233+
void* buf = pool_->allocate(size, align);
234+
235+
used += size;
236+
allocs++;
237+
238+
return buf;
239+
}
240+
241+
void Pmr_resource::do_deallocate(void* ptr, std::size_t s, std::size_t a) {
242+
deallocs++;
243+
pool_->deallocate(ptr,s,a);
244+
used -= s;
245+
}
246+
247+
bool Pmr_resource::do_is_equal(const std::pmr::memory_resource& other) const noexcept {
248+
if (const auto* other_ptr = dynamic_cast<const Pmr_resource*>(&other)) {
249+
return pool_ == other_ptr->pool_;
250+
}
251+
return false;
252+
}
253+
254+
bool Pmr_resource::empty() {
255+
return used >= capacity();
256+
}
257+
}
258+
259+
#endif

src/util/pmr_default.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
2+
#include <util/alloc_pmr.hpp>
3+
4+
std::pmr::memory_resource* std::pmr::get_default_resource() noexcept {
5+
static os::mem::Default_pmr* default_pmr;
6+
if (default_pmr == nullptr)
7+
default_pmr = new os::mem::Default_pmr{};
8+
return default_pmr;
9+
}

test/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@ set(TEST_SOURCES
158158
${TEST}/util/unit/bitops.cpp
159159
${TEST}/util/unit/lstack.cpp
160160
${TEST}/util/unit/buddy_alloc_test.cpp
161+
${TEST}/util/unit/pmr_alloc_test.cpp
161162
)
162163

163164
set(OS_SOURCES
@@ -241,6 +242,7 @@ set(OS_SOURCES
241242
${SRC}/util/syslogd.cpp
242243
${SRC}/util/tar.cpp
243244
${SRC}/util/uri.cpp
245+
# ${SRC}/util/pmr_default.cpp # <- older libc++ versions might need this
244246
${SRC}/virtio/virtio_queue.cpp
245247
)
246248

0 commit comments

Comments
 (0)