Skip to content

Commit a88c8e6

Browse files
author
Dian-Lun (Aaron) Lin
authored
Allocator handle for custom allocators used in shared library (#84)
This PR is specifically designed to support a custom allocator that works with a shared library. It offers an interface that enables the implementation of a custom allocator not included in the shared library. Which custom allocator to use is determined at runtime. This PR provides three utility functions: 1. `make_allocator_handle(Alloc alloc)`: Accepts an allocator and returns an `AllocatorHandle` to wrap the allocator. 2. `make_blocked_allocator_handle(Alloc alloc)`: Accepts an allocator, wraps it in an `AllocatorHandle`, and returns a `Blocked` object that manages the allocator. 3. `make_blocked_allocator_handle(const BlockingParameters& parameters, Alloc alloc)`: Accepts an allocator, wraps it in an `AllocatorHandle`, and returns a `Blocked` object configured with the specified parameters. The `AllocatorHandle` provides `allocate` and `deallocate` functions to use the wrapped custom allocator for memory allocation.
1 parent c09c8f5 commit a88c8e6

File tree

4 files changed

+254
-0
lines changed

4 files changed

+254
-0
lines changed

include/svs/core/allocator.h

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -507,4 +507,123 @@ class MemoryMapper {
507507
return MMapPtr<void>(base, bytes.value());
508508
}
509509
};
510+
511+
namespace detail {
512+
513+
template <typename Alloc>
514+
concept HasValueType = requires { typename Alloc::value_type; };
515+
516+
// clang-format off
517+
template <typename Alloc>
518+
concept Allocator = HasValueType<Alloc> && std::is_copy_constructible_v<Alloc>
519+
&& requires(Alloc& alloc, typename Alloc::value_type* ptr, size_t n) {
520+
{ alloc.allocate(n) } -> std::same_as<typename Alloc::value_type*>;
521+
522+
{ alloc.deallocate(ptr, n) };
523+
};
524+
// clang-format on
525+
526+
} // namespace detail
527+
528+
/////
529+
///// Type erasure allocator handle implementation
530+
/////
531+
532+
class AllocatorInterface {
533+
public:
534+
virtual ~AllocatorInterface() = default;
535+
virtual void* allocate(size_t n) = 0;
536+
virtual void deallocate(void* ptr, size_t n) = 0;
537+
538+
// covariant return type
539+
virtual AllocatorInterface* clone() const = 0;
540+
};
541+
542+
template <detail::Allocator Impl> class AllocatorImpl : public AllocatorInterface {
543+
public:
544+
// pass by value due to clone()
545+
explicit AllocatorImpl(Impl impl)
546+
: AllocatorInterface{}
547+
, impl_{std::move(impl)} {}
548+
549+
void* allocate(size_t n) override { return static_cast<void*>(impl_.allocate(n)); }
550+
551+
void deallocate(void* ptr, size_t n) override {
552+
impl_.deallocate(static_cast<typename Impl::value_type*>(ptr), n);
553+
}
554+
555+
AllocatorImpl<Impl>* clone() const override { return new AllocatorImpl(impl_); }
556+
557+
private:
558+
Impl impl_;
559+
};
560+
561+
template <typename T> class AllocatorHandle {
562+
public:
563+
using value_type = T;
564+
565+
template <detail::Allocator Impl>
566+
explicit AllocatorHandle(Impl&& impl)
567+
requires(!std::is_same_v<Impl, AllocatorHandle>) &&
568+
std::is_rvalue_reference_v<Impl&&> &&
569+
std::is_same_v<typename Impl::value_type, T>
570+
: impl_{new AllocatorImpl(std::move(impl))} {}
571+
572+
AllocatorHandle() {}
573+
AllocatorHandle(const AllocatorHandle& other)
574+
: impl_{other.impl_->clone()} {}
575+
AllocatorHandle(AllocatorHandle&&) = default;
576+
AllocatorHandle& operator=(const AllocatorHandle& other) {
577+
impl_.reset(other.impl_->clone());
578+
return *this;
579+
}
580+
AllocatorHandle& operator=(AllocatorHandle&&) = default;
581+
~AllocatorHandle() = default;
582+
583+
T* allocate(size_t n) {
584+
if (impl_.get() == nullptr) {
585+
throw ANNEXCEPTION("Empty allocator handle!");
586+
}
587+
return static_cast<T*>(impl_->allocate(n));
588+
}
589+
590+
void deallocate(T* ptr, size_t n) {
591+
if (impl_.get() == nullptr) {
592+
throw ANNEXCEPTION("Empty allocator handle!");
593+
}
594+
impl_->deallocate(static_cast<void*>(ptr), n);
595+
}
596+
597+
private:
598+
std::unique_ptr<AllocatorInterface> impl_;
599+
};
600+
601+
///
602+
/// @class allocator_handle
603+
///
604+
/// AllocatorHandle
605+
/// ================
606+
/// `AllocatorHandle` is designed to support a custom allocator that works with the
607+
/// shared library. It offers an interface that enables the
608+
/// implementation of a custom allocator not included in the shared library. Which custom
609+
/// allocator to use is determined at runtime.
610+
///
611+
612+
///
613+
/// @brief Creates an `AllocatorHandle` from a given allocator.
614+
///
615+
/// @tparam Alloc The type of the allocator, which must satisfy the `Allocator` concept.
616+
//
617+
/// @param alloc The allocator to be wrapped in an `AllocatorHandle`.
618+
/// @return An `AllocatorHandle` that manages the given allocator.
619+
///
620+
/// @copydoc allocator_handle
621+
///
622+
/// @sa make_blocked_allocator_handle
623+
///
624+
template <detail::Allocator Alloc>
625+
AllocatorHandle<typename Alloc::value_type> make_allocator_handle(Alloc alloc) {
626+
return AllocatorHandle<typename Alloc::value_type>{std::move(alloc)};
627+
}
628+
510629
} // namespace svs

include/svs/core/data/simple.h

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -826,4 +826,44 @@ template <typename T, size_t Extent = Dynamic, typename Alloc = lib::Allocator<T
826826
using BlockedData = SimpleData<T, Extent, Blocked<Alloc>>;
827827

828828
} // namespace data
829+
830+
///
831+
/// @brief Creates a `Blocked` object with an `AllocatorHandle` from a given allocator.
832+
///
833+
/// @tparam Alloc The type of the allocator, which must satisfy the `Allocator` concept.
834+
/// @param alloc The allocator to be wrapped in an `AllocatorHandle` and managed by a
835+
/// `Blocked` object.
836+
/// @return A `Blocked` object that manages an `AllocatorHandle` wrapping the given
837+
/// allocator.
838+
///
839+
/// @copydoc allocator_handle
840+
///
841+
/// @sa make_allocator_handle
842+
///
843+
template <detail::Allocator Alloc>
844+
data::Blocked<AllocatorHandle<typename Alloc::value_type>>
845+
make_blocked_allocator_handle(Alloc alloc) {
846+
return data::Blocked{make_allocator_handle(std::move(alloc))};
847+
}
848+
849+
///
850+
/// @brief Creates a `Blocked` object with an `AllocatorHandle` from a given allocator.
851+
///
852+
/// @tparam Alloc The type of the allocator, which must satisfy the `Allocator` concept.
853+
// @param parameters The blocking parameters used to configure the `Blocked` object.
854+
/// @param alloc The allocator to be wrapped in an `AllocatorHandle and managed by a
855+
/// `Blocked` object.
856+
/// @return A `Blocked` object that manages an `AllocatorHandle` wrapping the given
857+
/// allocator.
858+
///
859+
/// @copydoc allocator_handle
860+
///
861+
/// @sa make_allocator_handle
862+
///
863+
template <detail::Allocator Alloc>
864+
data::Blocked<AllocatorHandle<typename Alloc::value_type>>
865+
make_blocked_allocator_handle(const data::BlockingParameters& parameters, Alloc alloc) {
866+
return data::Blocked{parameters, make_allocator_handle(std::move(alloc))};
867+
}
868+
829869
} // namespace svs

tests/svs/core/allocator.cpp

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,4 +137,64 @@ CATCH_TEST_CASE("Testing Allocator", "[allocators]") {
137137
}
138138
}
139139
}
140+
141+
CATCH_SECTION("Testing `AllocatorHandle`") {
142+
size_t num_elements = 1024;
143+
CATCH_SECTION("Allocator") {
144+
auto alloc = svs::make_allocator_handle(svs::lib::Allocator<float>());
145+
auto* ptr = alloc.allocate(num_elements);
146+
147+
alloc.deallocate(ptr, num_elements);
148+
149+
CATCH_STATIC_REQUIRE(std::is_same_v<decltype(ptr), float*>);
150+
}
151+
CATCH_SECTION("HugepageAllocator - std::byte") {
152+
auto alloc = svs::make_allocator_handle(svs::HugepageAllocator<std::byte>());
153+
auto* ptr = alloc.allocate(num_elements);
154+
155+
auto allocations = svs::detail::GenericHugepageAllocator::get_allocations();
156+
CATCH_REQUIRE(allocations.size() == 1);
157+
CATCH_REQUIRE(allocations.contains(ptr));
158+
CATCH_REQUIRE(allocations.at(ptr) >= sizeof(std::byte) * num_elements);
159+
160+
alloc.deallocate(ptr, num_elements);
161+
allocations = svs::detail::GenericHugepageAllocator::get_allocations();
162+
CATCH_REQUIRE(allocations.size() == 0);
163+
CATCH_REQUIRE(!allocations.contains(ptr));
164+
165+
CATCH_STATIC_REQUIRE(std::is_same_v<decltype(ptr), std::byte*>);
166+
}
167+
CATCH_SECTION("HugepageAllocator - int8_t") {
168+
auto alloc = svs::make_allocator_handle(svs::HugepageAllocator<int8_t>());
169+
auto* ptr = alloc.allocate(num_elements);
170+
171+
auto allocations = svs::detail::GenericHugepageAllocator::get_allocations();
172+
CATCH_REQUIRE(allocations.size() == 1);
173+
CATCH_REQUIRE(allocations.contains(ptr));
174+
CATCH_REQUIRE(allocations.at(ptr) >= sizeof(int8_t) * num_elements);
175+
176+
alloc.deallocate(ptr, num_elements);
177+
allocations = svs::detail::GenericHugepageAllocator::get_allocations();
178+
CATCH_REQUIRE(allocations.size() == 0);
179+
CATCH_REQUIRE(!allocations.contains(ptr));
180+
181+
CATCH_STATIC_REQUIRE(std::is_same_v<decltype(ptr), int8_t*>);
182+
}
183+
CATCH_SECTION("HugepageAllocator - svs::Float16") {
184+
auto alloc = svs::make_allocator_handle(svs::HugepageAllocator<svs::Float16>());
185+
auto* ptr = alloc.allocate(num_elements);
186+
187+
auto allocations = svs::detail::GenericHugepageAllocator::get_allocations();
188+
CATCH_REQUIRE(allocations.size() == 1);
189+
CATCH_REQUIRE(allocations.contains(ptr));
190+
CATCH_REQUIRE(allocations.at(ptr) >= sizeof(svs::Float16) * num_elements);
191+
192+
alloc.deallocate(ptr, num_elements);
193+
allocations = svs::detail::GenericHugepageAllocator::get_allocations();
194+
CATCH_REQUIRE(allocations.size() == 0);
195+
CATCH_REQUIRE(!allocations.contains(ptr));
196+
197+
CATCH_STATIC_REQUIRE(std::is_same_v<decltype(ptr), svs::Float16*>);
198+
}
199+
}
140200
}

tests/svs/core/compact.cpp

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,4 +125,39 @@ CATCH_TEST_CASE("Simple Data Compaction", "[core][compaction]") {
125125
CATCH_REQUIRE(check_line(data.get_datum(i), val));
126126
}
127127
}
128+
129+
CATCH_SECTION("Blocked Data with AllocatorHandle") {
130+
auto data = svs::data::SimpleData<
131+
float,
132+
svs::Dynamic,
133+
svs::data::Blocked<svs::AllocatorHandle<float>>>(
134+
100, 20, svs::make_blocked_allocator_handle(svs::lib::Allocator<float>())
135+
);
136+
sequential_fill(data);
137+
CATCH_REQUIRE(check_sequential(data));
138+
139+
auto new_to_old = std::vector<uint32_t>{};
140+
for (size_t i = 0, imax = data.size() / 3; i < imax; ++i) {
141+
new_to_old.push_back(3 * i);
142+
}
143+
144+
// Single-threaded version
145+
data.compact(svs::lib::as_const_span(new_to_old), 20);
146+
for (size_t i = 0, imax = new_to_old.size(); i < imax; ++i) {
147+
auto val = new_to_old.at(i);
148+
CATCH_REQUIRE(check_line(data.get_datum(i), val));
149+
}
150+
151+
// Multi-threaded version.
152+
data.resize(0);
153+
data.resize(100);
154+
sequential_fill(data);
155+
CATCH_REQUIRE(check_sequential(data));
156+
auto tpool = svs::threads::DefaultThreadPool(2);
157+
data.compact(svs::lib::as_const_span(new_to_old), tpool, 20);
158+
for (size_t i = 0, imax = new_to_old.size(); i < imax; ++i) {
159+
auto val = new_to_old.at(i);
160+
CATCH_REQUIRE(check_line(data.get_datum(i), val));
161+
}
162+
}
128163
}

0 commit comments

Comments
 (0)