Skip to content

Commit 3f2daac

Browse files
authored
merge develop
2 parents 053f1bf + e9a90ee commit 3f2daac

16 files changed

+802
-62
lines changed

.github/workflows/code_testing.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ jobs:
6464
uses: dice-group/cpp-conan-release-reusable-workflow/.github/actions/add_conan_provider@main
6565

6666
- name: Configure CMake
67-
run: cmake -DCMAKE_CXX_FLAGS="-fsanitize=address,undefined" -DCMAKE_BUILD_TYPE=Debug -DWITH_SVECTOR=ON -DWITH_BOOST=ON -DBUILD_TESTING=On -DBUILD_EXAMPLES=On -DCMAKE_PROJECT_TOP_LEVEL_INCLUDES=conan_provider.cmake -G Ninja -B build .
67+
run: cmake -DCMAKE_CXX_FLAGS="-fsanitize=address,undefined" -DCMAKE_BUILD_TYPE=Debug -DWITH_SVECTOR=ON -DWITH_BOOST=ON -DBUILD_TESTING=On -DBUILD_EXAMPLES=On -DCMAKE_COMPILE_WARNING_AS_ERROR=ON -DCMAKE_PROJECT_TOP_LEVEL_INCLUDES=conan_provider.cmake -G Ninja -B build .
6868
env:
6969
CC: ${{ steps.install_cc.outputs.cc }}
7070
CXX: ${{ steps.install_cc.outputs.cxx }}

CMakeLists.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.24)
22

33
project(
44
dice-template-library
5-
VERSION 1.9.1
5+
VERSION 1.10.0
66
DESCRIPTION
77
"This template library is a collection of template-oriented code that we, the Data Science Group at UPB, found pretty handy. It contains: `switch_cases` (Use runtime values in compile-time context), `integral_template_tuple` (Create a tuple-like structure that instantiates a template for a range of values), `integral_template_variant` (A wrapper type for `std::variant` guarantees to only contain variants of the form `T<IX>` and `for_{types,values,range}` (Compile time for loops for types, values or ranges))."
88
HOMEPAGE_URL "https://dice-research.org/")

README.md

+14
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ It contains:
99
- `integral_template_variant`: A wrapper type for `std::variant` guarantees to only contain variants of the form `T<ix>` where $\texttt{ix}\in [\texttt{first},\texttt{last}]$ (inclusive).
1010
- `for_{types,values,range}`: Compile time for loops for types, values or ranges
1111
- `polymorphic_allocator`: Like `std::pmr::polymorphic_allocator` but with static dispatch
12+
- `limit_allocator`: Allocator wrapper that limits the amount of memory that is allowed to be allocated
13+
- `pool` & `pool_allocator`: Arena/pool allocator optimized for a limited number of known allocation sizes.
1214
- `DICE_DEFER`/`DICE_DEFER_TO_SUCCES`/`DICE_DEFER_TO_FAIL`: On-the-fly RAII for types that do not support it natively (similar to go's `defer` keyword)
1315
- `overloaded`: Composition for `std::variant` visitor lambdas
1416
- `flex_array`: A combination of `std::array`, `std::span` and a `vector` with small buffer optimization
@@ -64,6 +66,15 @@ The problem with `mmap` allocations is that they will be placed at an arbitrary
6466
therefore absolute pointers will cause segfaults if the segment is reloaded.
6567
Which means: vtables will not work (because they use absolute pointers) and therefore you cannot use `std::pmr::polymorphic_allocator`.
6668

69+
### `limit_allocator`
70+
Allocator wrapper that limits the amount of memory that can be allocated through the inner allocator.
71+
If the limit is exceeded it will throw `std::bad_alloc`.
72+
73+
### `pool_allocator`
74+
A memory arena/pool allocator with configurable allocation sizes. This is implemented
75+
as a collection of pools with varying allocation sizes. Allocations that do not
76+
fit into any of its pools are directly served via `new`.
77+
6778
### `DICE_DEFER`/`DICE_DEFER_TO_SUCCES`/`DICE_DEFER_TO_FAIL`
6879
A mechanism similar to go's `defer` keyword, which can be used to defer some action to scope exit.
6980
The primary use-case for this is on-the-fly RAII-like resource management for types that do not support RAII (for example C types).
@@ -93,6 +104,9 @@ Like `std::variant` but specifically optimized for usage with two types/variants
93104
The internal representation is a `union` of the two types plus a 1 byte (3 state) discriminant.
94105
Additionally, `visit` does not involve any virtual function calls.
95106

107+
### `type_traits.hpp`
108+
Things that are missing in the standard library `<type_traits>` header.
109+
96110
### Further Examples
97111

98112
Compilable code examples can be found in [examples](./examples). The example build requires the cmake

examples/CMakeLists.txt

+13
Original file line numberDiff line numberDiff line change
@@ -80,3 +80,16 @@ target_link_libraries(example_variant2
8080
dice-template-library::dice-template-library
8181
)
8282

83+
add_executable(example_limit_allocator
84+
example_limit_allocator.cpp)
85+
target_link_libraries(example_limit_allocator
86+
PRIVATE
87+
dice-template-library::dice-template-library
88+
)
89+
90+
add_executable(example_pool_allocator
91+
example_pool_allocator.cpp)
92+
target_link_libraries(example_pool_allocator
93+
PRIVATE
94+
dice-template-library::dice-template-library
95+
)

examples/example_limit_allocator.cpp

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
#include <dice/template-library/limit_allocator.hpp>
2+
3+
#include <cassert>
4+
#include <vector>
5+
6+
7+
int main() {
8+
std::vector<int, dice::template_library::limit_allocator<int>> vec{dice::template_library::limit_allocator<int>{3 * sizeof(int)}};
9+
vec.push_back(1);
10+
vec.push_back(2);
11+
12+
try {
13+
vec.push_back(3);
14+
assert(false);
15+
} catch (...) {
16+
}
17+
18+
vec.pop_back();
19+
vec.push_back(4);
20+
21+
try {
22+
vec.push_back(5);
23+
assert(false);
24+
} catch (...) {
25+
}
26+
}

examples/example_pool_allocator.cpp

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
#include <dice/template-library/pool_allocator.hpp>
2+
3+
#include <cstddef>
4+
#include <cstdint>
5+
#include <iostream>
6+
#include <vector>
7+
8+
struct list {
9+
uint64_t elem;
10+
list *next;
11+
};
12+
13+
int main() {
14+
dice::template_library::pool_allocator<std::byte, sizeof(list)> alloc;
15+
16+
{ // efficient pool allocations for elements of known size
17+
dice::template_library::pool_allocator<list, sizeof(list)> list_alloc = alloc;
18+
19+
auto *head = list_alloc.allocate(1); // efficient pool allocation
20+
new (head) list{.elem = 0, .next = nullptr};
21+
22+
head->next = list_alloc.allocate(1); // efficient pool allocation
23+
new (head->next) list{.elem = 1, .next = nullptr};
24+
25+
auto const *cur = head;
26+
while (cur != nullptr) {
27+
std::cout << cur->elem << " ";
28+
cur = cur->next;
29+
}
30+
31+
list_alloc.deallocate(head->next, 1);
32+
list_alloc.deallocate(head, 1);
33+
}
34+
35+
{ // fallback allocation with new & support as container allocator
36+
std::vector<uint64_t, dice::template_library::pool_allocator<uint64_t, sizeof(list)>> vec(alloc);
37+
vec.resize(1024);
38+
}
39+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
#ifndef DICE_TEMPLATELIBRARY_LIMITALLOCATOR_HPP
2+
#define DICE_TEMPLATELIBRARY_LIMITALLOCATOR_HPP
3+
4+
#include <atomic>
5+
#include <cstddef>
6+
#include <memory>
7+
#include <new>
8+
#include <type_traits>
9+
#include <utility>
10+
11+
namespace dice::template_library {
12+
13+
/**
14+
* The synchronization policy of a limit_allocator
15+
*/
16+
enum struct limit_allocator_syncness : bool {
17+
sync, ///< thread-safe (synchronized)
18+
unsync, ///< not thread-safe (unsynchronized)
19+
};
20+
21+
namespace detail_limit_allocator {
22+
template<limit_allocator_syncness syncness>
23+
struct limit_allocator_control_block;
24+
25+
template<>
26+
struct limit_allocator_control_block<limit_allocator_syncness::sync> {
27+
std::atomic<size_t> bytes_left = 0;
28+
29+
void allocate(size_t n_bytes) {
30+
auto old = bytes_left.load(std::memory_order_relaxed);
31+
32+
do {
33+
if (old < n_bytes) [[unlikely]] {
34+
throw std::bad_alloc{};
35+
}
36+
} while (!bytes_left.compare_exchange_weak(old, old - n_bytes, std::memory_order_relaxed, std::memory_order_relaxed));
37+
}
38+
39+
void deallocate(size_t n_bytes) noexcept {
40+
bytes_left.fetch_add(n_bytes, std::memory_order_relaxed);
41+
}
42+
};
43+
44+
template<>
45+
struct limit_allocator_control_block<limit_allocator_syncness::unsync> {
46+
size_t bytes_left = 0;
47+
48+
void allocate(size_t n_bytes) {
49+
if (bytes_left < n_bytes) [[unlikely]] {
50+
throw std::bad_alloc{};
51+
}
52+
bytes_left -= n_bytes;
53+
}
54+
55+
void deallocate(size_t n_bytes) noexcept {
56+
bytes_left += n_bytes;
57+
}
58+
};
59+
}// namespace detail_limit_allocator
60+
61+
/**
62+
* Allocator wrapper that limits the amount of memory its underlying allocator
63+
* is allowed to allocate.
64+
*
65+
* @tparam T value type of the allocator (the thing that it allocates)
66+
* @tparam Allocator the underlying allocator
67+
* @tparam syncness determines the synchronization of the limit
68+
*/
69+
template<typename T, template<typename> typename Allocator = std::allocator, limit_allocator_syncness syncness = limit_allocator_syncness::sync>
70+
struct limit_allocator {
71+
using control_block_type = detail_limit_allocator::limit_allocator_control_block<syncness>;
72+
using value_type = T;
73+
using upstream_allocator_type = Allocator<T>;
74+
using pointer = typename std::allocator_traits<upstream_allocator_type>::pointer;
75+
using const_pointer = typename std::allocator_traits<upstream_allocator_type>::const_pointer;
76+
using void_pointer = typename std::allocator_traits<upstream_allocator_type>::void_pointer;
77+
using const_void_pointer = typename std::allocator_traits<upstream_allocator_type>::const_void_pointer;
78+
using size_type = typename std::allocator_traits<upstream_allocator_type>::size_type;
79+
using difference_type = typename std::allocator_traits<upstream_allocator_type>::difference_type;
80+
81+
using propagate_on_container_copy_assignment = typename std::allocator_traits<upstream_allocator_type>::propagate_on_container_copy_assignment;
82+
using propagate_on_container_move_assignment = typename std::allocator_traits<upstream_allocator_type>::propagate_on_container_move_assignment;
83+
using propagate_on_container_swap = typename std::allocator_traits<upstream_allocator_type>::propagate_on_container_swap;
84+
using is_always_equal = std::false_type;
85+
86+
template<typename U>
87+
struct rebind {
88+
using other = limit_allocator<U, Allocator, syncness>;
89+
};
90+
91+
private:
92+
template<typename, template<typename> typename, limit_allocator_syncness>
93+
friend struct limit_allocator;
94+
95+
std::shared_ptr<control_block_type> control_block_;
96+
[[no_unique_address]] upstream_allocator_type inner_;
97+
98+
constexpr limit_allocator(std::shared_ptr<control_block_type> const &control_block, upstream_allocator_type const &alloc)
99+
requires(std::is_default_constructible_v<upstream_allocator_type>)
100+
: control_block_{control_block},
101+
inner_{alloc} {
102+
}
103+
104+
public:
105+
explicit constexpr limit_allocator(size_t bytes_limit)
106+
requires(std::is_default_constructible_v<upstream_allocator_type>)
107+
: control_block_{std::make_shared<control_block_type>(bytes_limit)},
108+
inner_{} {
109+
}
110+
111+
constexpr limit_allocator(limit_allocator const &other) noexcept(std::is_nothrow_move_constructible_v<upstream_allocator_type>) = default;
112+
constexpr limit_allocator(limit_allocator &&other) noexcept(std::is_nothrow_copy_constructible_v<upstream_allocator_type>) = default;
113+
constexpr limit_allocator &operator=(limit_allocator const &other) noexcept(std::is_nothrow_copy_assignable_v<upstream_allocator_type>) = default;
114+
constexpr limit_allocator &operator=(limit_allocator &&other) noexcept(std::is_nothrow_move_assignable_v<upstream_allocator_type>) = default;
115+
constexpr ~limit_allocator() = default;
116+
117+
template<typename U>
118+
constexpr limit_allocator(limit_allocator<U, Allocator> const &other) noexcept(std::is_nothrow_constructible_v<upstream_allocator_type, typename limit_allocator<U, Allocator>::upstream_allocator_type const &>)
119+
: control_block_{other.control_block_},
120+
inner_{other.inner_} {
121+
}
122+
123+
constexpr limit_allocator(size_t bytes_limit, upstream_allocator_type const &upstream)
124+
: control_block_{std::make_shared<control_block_type>(bytes_limit)},
125+
inner_{upstream} {
126+
}
127+
128+
constexpr limit_allocator(size_t bytes_limit, upstream_allocator_type &&upstream)
129+
: control_block_{std::make_shared<control_block_type>(bytes_limit)},
130+
inner_{std::move(upstream)} {
131+
}
132+
133+
template<typename... Args>
134+
explicit constexpr limit_allocator(size_t bytes_limit, std::in_place_t, Args &&...args)
135+
: control_block_{std::make_shared<control_block_type>(bytes_limit)},
136+
inner_{std::forward<Args>(args)...} {
137+
}
138+
139+
constexpr pointer allocate(size_t n) {
140+
control_block_->allocate(n * sizeof(T));
141+
142+
try {
143+
return std::allocator_traits<upstream_allocator_type>::allocate(inner_, n);
144+
} catch (...) {
145+
control_block_->deallocate(n * sizeof(T));
146+
throw;
147+
}
148+
}
149+
150+
constexpr void deallocate(pointer ptr, size_t n) {
151+
std::allocator_traits<upstream_allocator_type>::deallocate(inner_, ptr, n);
152+
control_block_->deallocate(n * sizeof(T));
153+
}
154+
155+
constexpr limit_allocator select_on_container_copy_construction() const {
156+
return limit_allocator{control_block_, std::allocator_traits<upstream_allocator_type>::select_on_container_copy_construction(inner_)};
157+
}
158+
159+
[[nodiscard]] upstream_allocator_type const &upstream_allocator() const noexcept {
160+
return inner_;
161+
}
162+
163+
friend constexpr void swap(limit_allocator &a, limit_allocator &b) noexcept(std::is_nothrow_swappable_v<upstream_allocator_type>)
164+
requires(std::is_swappable_v<upstream_allocator_type>)
165+
{
166+
using std::swap;
167+
swap(a.control_block_, b.control_block_);
168+
swap(a.inner_, b.inner_);
169+
}
170+
171+
bool operator==(limit_allocator const &other) const noexcept = default;
172+
bool operator!=(limit_allocator const &other) const noexcept = default;
173+
};
174+
}// namespace dice::template_library
175+
176+
#endif// DICE_TEMPLATELIBRARY_LIMITALLOCATOR_HPP

0 commit comments

Comments
 (0)