Skip to content

Commit

Permalink
(wip) reorganizing fallback allocator.
Browse files Browse the repository at this point in the history
  • Loading branch information
Keita Iwabuchi committed Feb 15, 2024
1 parent 881239a commit 1b2f76e
Show file tree
Hide file tree
Showing 7 changed files with 320 additions and 273 deletions.
2 changes: 1 addition & 1 deletion example/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ add_metall_executable(snapshot snapshot.cpp)

add_metall_executable(csr_graph csr_graph.cpp)

add_metall_executable(fallback_allocator_adaptor fallback_allocator_adaptor.cpp)
add_metall_executable(fallback_allocator fallback_allocator.cpp)

add_metall_executable(datastore_description datastore_description.cpp)

Expand Down
4 changes: 2 additions & 2 deletions example/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,9 +96,9 @@ Therefore, the data structures in the examples below are more like how to use ST

### Fallback Allocator

* [fallback_allocator_adaptor.cpp](fallback_allocator_adaptor.cpp)
* [fallback_allocator.cpp](fallback_allocator.cpp)

* How to use [fallback_allocator_adaptor](../include/metall/utility/fallback_allocator_adaptor.hpp)
* How to use [fallback_allocator](../include/metall/container/fallback_allocator.hpp)


### MPI (experimental implementation)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,13 @@
#include <boost/container/vector.hpp>

#include <metall/metall.hpp>
#include <metall/utility/fallback_allocator_adaptor.hpp>

// The line below is the only change required to use fallback_allocator_adaptor.
// Wraps up 'metall::manager::allocator_type<..>' with
// fallback_allocator_adaptor.
using allocator_t = metall::utility::fallback_allocator_adaptor<
metall::manager::allocator_type<int>>;
#include <metall/container/fallback_allocator.hpp>

using allocator_t = metall::container::fallback_allocator<int>;
using vector_t = boost::container::vector<int, allocator_t>;

int main() {
// Allocation with Metall
// The code below works with both 'fallback_allocator_adaptor<..>' and
// 'metall::manager::allocator_type<...>'.
{
metall::manager manager(metall::create_only, "/tmp/dir");
auto pvec = manager.construct<vector_t>("vec")(manager.get_allocator());
Expand All @@ -30,7 +23,7 @@ int main() {
}

// Allocation w/o Metall, i.e., uses a heap allocator such as malloc().
// This code causes a build error if fallback_allocator_adaptor is not used.
// This code would cause a build error if fallback_allocator were not used.
{
vector_t vec;
vec.push_back(2);
Expand Down
305 changes: 305 additions & 0 deletions include/metall/container/fallback_allocator.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,305 @@
// Copyright 2024 Lawrence Livermore National Security, LLC and other Metall
// Project Developers. See the top-level COPYRIGHT file for details.
//
// SPDX-License-Identifier: (Apache-2.0 OR MIT)

#ifndef METALL_CONTAINER_FALLBACK_ALLOCATOR_HPP
#define METALL_CONTAINER_FALLBACK_ALLOCATOR_HPP

#include <memory>
#include <cstdlib>
#include <type_traits>

#include <metall/metall.hpp>

namespace metall::container {

/// Forward declaration
template <typename stateful_allocator>
class fallback_allocator_adaptor;

/// \brief A STL compatible allocator which fallbacks to a heap allocator (e.g.,
/// malloc()) if no argument is provided to construct a metall::stl_allocator
/// instance.
///
/// \tparam T The type of the object to allocate
///
/// \details
/// This allocator enables the following code.
/// \code
/// {
/// using alloc = fallback_allocator<int>;
/// // Allocate a vector object in a heap.
/// vector<int, alloc> vec;
/// // Allocate a vector object in a Metall space.
/// vector<int, alloc> vec2(manager.get_allocator());
/// }
/// \endcode
/// \attention
/// One of the primary purposes of this allocator is to provide a way to
/// temporarily allocate data structures that use Metall’s STL-allocator in a
/// heap in addition to in Metall memory space. It is advised to use this
/// allocator with caution as this two memory spaces are used transparently by
/// this allocator.
template <typename T>
using fallback_allocator =
fallback_allocator_adaptor<metall::manager::allocator_type<T>>;

/// \brief A STL compatible allocator which fallbacks to a heap allocator (e.g.,
/// malloc()) if its constructor receives no argument to construct the stateful
/// allocator instance.
/// \tparam stateful_allocator The stateful allocator type. It must not be
/// default constructible.
template <typename stateful_allocator>
class fallback_allocator_adaptor {
// Check if the stateful_allocator takes arugments in its constructor
static_assert(!std::is_constructible<stateful_allocator>::value,
"The stateful allocator must not be default constructible");

private:
template <typename T>
using other_stateful_allocatorator_type = typename std::allocator_traits<
stateful_allocator>::template rebind_alloc<T>;

public:
// -------------------- //
// Public types and static values
// -------------------- //
using stateful_allocatorator_type = typename std::remove_const<
typename std::remove_reference<stateful_allocator>::type>::type;

using value_type = typename stateful_allocatorator_type::value_type;
using pointer = typename stateful_allocatorator_type::pointer;
using const_pointer = typename stateful_allocatorator_type::const_pointer;
using void_pointer = typename stateful_allocatorator_type::void_pointer;
using const_void_pointer =
typename stateful_allocatorator_type::const_void_pointer;
using difference_type = typename stateful_allocatorator_type::difference_type;
using size_type = typename stateful_allocatorator_type::size_type;

/// \brief Makes another allocator type for type T2
template <typename T2>
struct rebind {
using other =
fallback_allocator_adaptor<other_stateful_allocatorator_type<T2>>;
};

public:
// -------------------- //
// Constructor & assign operator
// -------------------- //

/// \brief Default constructor which falls back on the regular allocator
/// (i.e., malloc()).
fallback_allocator_adaptor() noexcept : m_stateful_allocatorator(nullptr) {}

/// \brief Construct a new instance using an instance of
/// fallback_allocator_adaptor with any stateful_allocatorator type.
template <typename stateful_allocatorator_type2,
std::enable_if_t<
std::is_constructible<stateful_allocator,
stateful_allocatorator_type2>::value,
int> = 0>
fallback_allocator_adaptor(
fallback_allocator_adaptor<stateful_allocatorator_type2>
allocator_instance) noexcept
: m_stateful_allocatorator(allocator_instance.stateful_allocatorator()) {}

/// \brief Construct a new instance using an instance of any
/// stateful_allocatorator.
template <typename stateful_allocatorator_type2,
std::enable_if_t<
std::is_constructible<stateful_allocator,
stateful_allocatorator_type2>::value,
int> = 0>
fallback_allocator_adaptor(
stateful_allocatorator_type2 allocator_instance) noexcept
: m_stateful_allocatorator(allocator_instance) {}

/// \brief Copy constructor
fallback_allocator_adaptor(const fallback_allocator_adaptor &other) noexcept =
default;

/// \brief Move constructor
fallback_allocator_adaptor(fallback_allocator_adaptor &&other) noexcept =
default;

/// \brief Copy assign operator
fallback_allocator_adaptor &operator=(
const fallback_allocator_adaptor &) noexcept = default;

/// \brief Copy assign operator, using an instance of
/// fallback_allocator_adaptor with any stateful_allocatorator type
template <typename stateful_allocatorator_type2,
std::enable_if_t<
std::is_constructible<stateful_allocator,
stateful_allocatorator_type2>::value,
int> = 0>
fallback_allocator_adaptor &operator=(
const fallback_allocator_adaptor<stateful_allocatorator_type2>
&other) noexcept {
m_stateful_allocatorator = other.stateful_allocatorator();
return *this;
}

/// \brief Copy assign operator for any stateful_allocatorator
template <typename stateful_allocatorator_type2,
std::enable_if_t<
std::is_constructible<stateful_allocator,
stateful_allocatorator_type2>::value,
int> = 0>
fallback_allocator_adaptor &operator=(
const stateful_allocatorator_type2 &allocator_instance) noexcept {
m_stateful_allocatorator = allocator_instance;
return *this;
}

/// \brief Move assign operator
fallback_allocator_adaptor &operator=(
fallback_allocator_adaptor &&other) noexcept = default;

/// \brief Move assign operator, using an instance of
/// fallback_allocator_adaptor with any stateful_allocatorator type
template <typename stateful_allocatorator_type2,
std::enable_if_t<
std::is_constructible<stateful_allocator,
stateful_allocatorator_type2>::value,
int> = 0>
fallback_allocator_adaptor &operator=(
fallback_allocator_adaptor<stateful_allocatorator_type2>
&&other) noexcept {
m_stateful_allocatorator = std::move(other.stateful_allocatorator());
return *this;
}

/// \brief Move assign operator for any stateful_allocatorator
template <typename stateful_allocatorator_type2,
std::enable_if_t<
std::is_constructible<stateful_allocator,
stateful_allocatorator_type2>::value,
int> = 0>
fallback_allocator_adaptor &operator=(
stateful_allocatorator_type2 &&allocator_instance) noexcept {
m_stateful_allocatorator = std::move(allocator_instance);
return *this;
}

/// \brief Allocates n * sizeof(T) bytes of storage
/// \param n The size to allocation
/// \return Returns a pointer
pointer allocate(const size_type n) const {
if (priv_stateful_allocatorator_available()) {
return m_stateful_allocatorator.allocate(n);
}
return priv_fallback_allocate(n);
}

/// \brief Deallocates the storage reference by the pointer ptr
/// \param ptr A pointer to the storage
/// \param size The size of the storage
void deallocate(pointer ptr, const size_type size) const {
if (priv_stateful_allocatorator_available()) {
m_stateful_allocatorator.deallocate(ptr, size);
} else {
priv_fallback_deallocate(ptr);
}
}

/// \brief The size of the theoretical maximum allocation size
/// \return The size of the theoretical maximum allocation size
size_type max_size() const noexcept {
return m_stateful_allocatorator.max_size();
}

/// \brief Constructs an object of T
/// \tparam Args The types of the constructor arguments
/// \param ptr A pointer to allocated storage
/// \param args The constructor arguments to use
template <class... Args>
void construct(const pointer &ptr, Args &&...args) const {
if (priv_stateful_allocatorator_available()) {
m_stateful_allocatorator.construct(ptr, std::forward<Args>(args)...);
} else {
priv_fallback_construct(ptr, std::forward<Args>(args)...);
}
}

/// \brief Deconstruct an object of T
/// \param ptr A pointer to the object
void destroy(const pointer &ptr) const {
if (priv_stateful_allocatorator_available()) {
m_stateful_allocatorator.destroy(ptr);
} else {
priv_fallback_destroy(ptr);
}
}

// ---------- This class's unique public functions ---------- //
stateful_allocatorator_type &stateful_allocatorator() {
return m_stateful_allocatorator;
}

const stateful_allocatorator_type &stateful_allocatorator() const {
return m_stateful_allocatorator;
}

private:
// -------------------- //
// Private methods
// -------------------- //
auto priv_stateful_allocatorator_available() const {
return !!(m_stateful_allocatorator.get_pointer_to_manager_kernel());
}

pointer priv_fallback_allocate(const size_type n) const {
if (max_size() < n) {
throw std::bad_array_new_length();
}

void *const addr = std::malloc(n * sizeof(value_type));
if (!addr) {
throw std::bad_alloc();
}

return pointer(static_cast<value_type *>(addr));
}

void priv_fallback_deallocate(pointer ptr) const {
std::free(to_raw_pointer(ptr));
}

void priv_fallback_destroy(pointer ptr) const { (*ptr).~value_type(); }

template <class... arg_types>
void priv_fallback_construct(const pointer &ptr, arg_types &&...args) const {
::new ((void *)to_raw_pointer(ptr))
value_type(std::forward<arg_types>(args)...);
}

// -------------------- //
// Private fields
// -------------------- //
stateful_allocatorator_type m_stateful_allocatorator;
};

template <typename stateful_allocatorator_type>
inline bool operator==(
const fallback_allocator_adaptor<stateful_allocatorator_type> &rhd,
const fallback_allocator_adaptor<stateful_allocatorator_type> &lhd) {
// Return true if they point to the same manager kernel
return rhd.stateful_allocatorator() == lhd.stateful_allocatorator();
}

template <typename stateful_allocatorator_type>
inline bool operator!=(
const fallback_allocator_adaptor<stateful_allocatorator_type> &rhd,
const fallback_allocator_adaptor<stateful_allocatorator_type> &lhd) {
return !(rhd == lhd);
}

} // namespace metall::container

/// \example fallback_allocator.cpp
/// This is an example of how to use fallback_allocator.

#endif // METALL_CONTAINER_FALLBACK_ALLOCATOR_HPP
Loading

0 comments on commit 1b2f76e

Please sign in to comment.