Skip to content

Commit

Permalink
merge develop
Browse files Browse the repository at this point in the history
  • Loading branch information
liss-h authored Jan 20, 2025
2 parents 053f1bf + e9a90ee commit 3f2daac
Show file tree
Hide file tree
Showing 16 changed files with 802 additions and 62 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/code_testing.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ jobs:
uses: dice-group/cpp-conan-release-reusable-workflow/.github/actions/add_conan_provider@main

- name: Configure CMake
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 .
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 .
env:
CC: ${{ steps.install_cc.outputs.cc }}
CXX: ${{ steps.install_cc.outputs.cxx }}
Expand Down
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.24)

project(
dice-template-library
VERSION 1.9.1
VERSION 1.10.0
DESCRIPTION
"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))."
HOMEPAGE_URL "https://dice-research.org/")
Expand Down
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ It contains:
- `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).
- `for_{types,values,range}`: Compile time for loops for types, values or ranges
- `polymorphic_allocator`: Like `std::pmr::polymorphic_allocator` but with static dispatch
- `limit_allocator`: Allocator wrapper that limits the amount of memory that is allowed to be allocated
- `pool` & `pool_allocator`: Arena/pool allocator optimized for a limited number of known allocation sizes.
- `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)
- `overloaded`: Composition for `std::variant` visitor lambdas
- `flex_array`: A combination of `std::array`, `std::span` and a `vector` with small buffer optimization
Expand Down Expand Up @@ -64,6 +66,15 @@ The problem with `mmap` allocations is that they will be placed at an arbitrary
therefore absolute pointers will cause segfaults if the segment is reloaded.
Which means: vtables will not work (because they use absolute pointers) and therefore you cannot use `std::pmr::polymorphic_allocator`.

### `limit_allocator`
Allocator wrapper that limits the amount of memory that can be allocated through the inner allocator.
If the limit is exceeded it will throw `std::bad_alloc`.

### `pool_allocator`
A memory arena/pool allocator with configurable allocation sizes. This is implemented
as a collection of pools with varying allocation sizes. Allocations that do not
fit into any of its pools are directly served via `new`.

### `DICE_DEFER`/`DICE_DEFER_TO_SUCCES`/`DICE_DEFER_TO_FAIL`
A mechanism similar to go's `defer` keyword, which can be used to defer some action to scope exit.
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).
Expand Down Expand Up @@ -93,6 +104,9 @@ Like `std::variant` but specifically optimized for usage with two types/variants
The internal representation is a `union` of the two types plus a 1 byte (3 state) discriminant.
Additionally, `visit` does not involve any virtual function calls.

### `type_traits.hpp`
Things that are missing in the standard library `<type_traits>` header.

### Further Examples

Compilable code examples can be found in [examples](./examples). The example build requires the cmake
Expand Down
13 changes: 13 additions & 0 deletions examples/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -80,3 +80,16 @@ target_link_libraries(example_variant2
dice-template-library::dice-template-library
)

add_executable(example_limit_allocator
example_limit_allocator.cpp)
target_link_libraries(example_limit_allocator
PRIVATE
dice-template-library::dice-template-library
)

add_executable(example_pool_allocator
example_pool_allocator.cpp)
target_link_libraries(example_pool_allocator
PRIVATE
dice-template-library::dice-template-library
)
26 changes: 26 additions & 0 deletions examples/example_limit_allocator.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#include <dice/template-library/limit_allocator.hpp>

#include <cassert>
#include <vector>


int main() {
std::vector<int, dice::template_library::limit_allocator<int>> vec{dice::template_library::limit_allocator<int>{3 * sizeof(int)}};
vec.push_back(1);
vec.push_back(2);

try {
vec.push_back(3);
assert(false);
} catch (...) {
}

vec.pop_back();
vec.push_back(4);

try {
vec.push_back(5);
assert(false);
} catch (...) {
}
}
39 changes: 39 additions & 0 deletions examples/example_pool_allocator.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#include <dice/template-library/pool_allocator.hpp>

#include <cstddef>
#include <cstdint>
#include <iostream>
#include <vector>

struct list {
uint64_t elem;
list *next;
};

int main() {
dice::template_library::pool_allocator<std::byte, sizeof(list)> alloc;

{ // efficient pool allocations for elements of known size
dice::template_library::pool_allocator<list, sizeof(list)> list_alloc = alloc;

auto *head = list_alloc.allocate(1); // efficient pool allocation
new (head) list{.elem = 0, .next = nullptr};

head->next = list_alloc.allocate(1); // efficient pool allocation
new (head->next) list{.elem = 1, .next = nullptr};

auto const *cur = head;
while (cur != nullptr) {
std::cout << cur->elem << " ";
cur = cur->next;
}

list_alloc.deallocate(head->next, 1);
list_alloc.deallocate(head, 1);
}

{ // fallback allocation with new & support as container allocator
std::vector<uint64_t, dice::template_library::pool_allocator<uint64_t, sizeof(list)>> vec(alloc);
vec.resize(1024);
}
}
176 changes: 176 additions & 0 deletions include/dice/template-library/limit_allocator.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
#ifndef DICE_TEMPLATELIBRARY_LIMITALLOCATOR_HPP
#define DICE_TEMPLATELIBRARY_LIMITALLOCATOR_HPP

#include <atomic>
#include <cstddef>
#include <memory>
#include <new>
#include <type_traits>
#include <utility>

namespace dice::template_library {

/**
* The synchronization policy of a limit_allocator
*/
enum struct limit_allocator_syncness : bool {
sync, ///< thread-safe (synchronized)
unsync, ///< not thread-safe (unsynchronized)
};

namespace detail_limit_allocator {
template<limit_allocator_syncness syncness>
struct limit_allocator_control_block;

template<>
struct limit_allocator_control_block<limit_allocator_syncness::sync> {
std::atomic<size_t> bytes_left = 0;

void allocate(size_t n_bytes) {
auto old = bytes_left.load(std::memory_order_relaxed);

do {
if (old < n_bytes) [[unlikely]] {
throw std::bad_alloc{};
}
} while (!bytes_left.compare_exchange_weak(old, old - n_bytes, std::memory_order_relaxed, std::memory_order_relaxed));
}

void deallocate(size_t n_bytes) noexcept {
bytes_left.fetch_add(n_bytes, std::memory_order_relaxed);
}
};

template<>
struct limit_allocator_control_block<limit_allocator_syncness::unsync> {
size_t bytes_left = 0;

void allocate(size_t n_bytes) {
if (bytes_left < n_bytes) [[unlikely]] {
throw std::bad_alloc{};
}
bytes_left -= n_bytes;
}

void deallocate(size_t n_bytes) noexcept {
bytes_left += n_bytes;
}
};
}// namespace detail_limit_allocator

/**
* Allocator wrapper that limits the amount of memory its underlying allocator
* is allowed to allocate.
*
* @tparam T value type of the allocator (the thing that it allocates)
* @tparam Allocator the underlying allocator
* @tparam syncness determines the synchronization of the limit
*/
template<typename T, template<typename> typename Allocator = std::allocator, limit_allocator_syncness syncness = limit_allocator_syncness::sync>
struct limit_allocator {
using control_block_type = detail_limit_allocator::limit_allocator_control_block<syncness>;
using value_type = T;
using upstream_allocator_type = Allocator<T>;
using pointer = typename std::allocator_traits<upstream_allocator_type>::pointer;
using const_pointer = typename std::allocator_traits<upstream_allocator_type>::const_pointer;
using void_pointer = typename std::allocator_traits<upstream_allocator_type>::void_pointer;
using const_void_pointer = typename std::allocator_traits<upstream_allocator_type>::const_void_pointer;
using size_type = typename std::allocator_traits<upstream_allocator_type>::size_type;
using difference_type = typename std::allocator_traits<upstream_allocator_type>::difference_type;

using propagate_on_container_copy_assignment = typename std::allocator_traits<upstream_allocator_type>::propagate_on_container_copy_assignment;
using propagate_on_container_move_assignment = typename std::allocator_traits<upstream_allocator_type>::propagate_on_container_move_assignment;
using propagate_on_container_swap = typename std::allocator_traits<upstream_allocator_type>::propagate_on_container_swap;
using is_always_equal = std::false_type;

template<typename U>
struct rebind {
using other = limit_allocator<U, Allocator, syncness>;
};

private:
template<typename, template<typename> typename, limit_allocator_syncness>
friend struct limit_allocator;

std::shared_ptr<control_block_type> control_block_;
[[no_unique_address]] upstream_allocator_type inner_;

constexpr limit_allocator(std::shared_ptr<control_block_type> const &control_block, upstream_allocator_type const &alloc)
requires(std::is_default_constructible_v<upstream_allocator_type>)
: control_block_{control_block},
inner_{alloc} {
}

public:
explicit constexpr limit_allocator(size_t bytes_limit)
requires(std::is_default_constructible_v<upstream_allocator_type>)
: control_block_{std::make_shared<control_block_type>(bytes_limit)},
inner_{} {
}

constexpr limit_allocator(limit_allocator const &other) noexcept(std::is_nothrow_move_constructible_v<upstream_allocator_type>) = default;
constexpr limit_allocator(limit_allocator &&other) noexcept(std::is_nothrow_copy_constructible_v<upstream_allocator_type>) = default;
constexpr limit_allocator &operator=(limit_allocator const &other) noexcept(std::is_nothrow_copy_assignable_v<upstream_allocator_type>) = default;
constexpr limit_allocator &operator=(limit_allocator &&other) noexcept(std::is_nothrow_move_assignable_v<upstream_allocator_type>) = default;
constexpr ~limit_allocator() = default;

template<typename U>
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 &>)
: control_block_{other.control_block_},
inner_{other.inner_} {
}

constexpr limit_allocator(size_t bytes_limit, upstream_allocator_type const &upstream)
: control_block_{std::make_shared<control_block_type>(bytes_limit)},
inner_{upstream} {
}

constexpr limit_allocator(size_t bytes_limit, upstream_allocator_type &&upstream)
: control_block_{std::make_shared<control_block_type>(bytes_limit)},
inner_{std::move(upstream)} {
}

template<typename... Args>
explicit constexpr limit_allocator(size_t bytes_limit, std::in_place_t, Args &&...args)
: control_block_{std::make_shared<control_block_type>(bytes_limit)},
inner_{std::forward<Args>(args)...} {
}

constexpr pointer allocate(size_t n) {
control_block_->allocate(n * sizeof(T));

try {
return std::allocator_traits<upstream_allocator_type>::allocate(inner_, n);
} catch (...) {
control_block_->deallocate(n * sizeof(T));
throw;
}
}

constexpr void deallocate(pointer ptr, size_t n) {
std::allocator_traits<upstream_allocator_type>::deallocate(inner_, ptr, n);
control_block_->deallocate(n * sizeof(T));
}

constexpr limit_allocator select_on_container_copy_construction() const {
return limit_allocator{control_block_, std::allocator_traits<upstream_allocator_type>::select_on_container_copy_construction(inner_)};
}

[[nodiscard]] upstream_allocator_type const &upstream_allocator() const noexcept {
return inner_;
}

friend constexpr void swap(limit_allocator &a, limit_allocator &b) noexcept(std::is_nothrow_swappable_v<upstream_allocator_type>)
requires(std::is_swappable_v<upstream_allocator_type>)
{
using std::swap;
swap(a.control_block_, b.control_block_);
swap(a.inner_, b.inner_);
}

bool operator==(limit_allocator const &other) const noexcept = default;
bool operator!=(limit_allocator const &other) const noexcept = default;
};
}// namespace dice::template_library

#endif// DICE_TEMPLATELIBRARY_LIMITALLOCATOR_HPP
Loading

0 comments on commit 3f2daac

Please sign in to comment.