Skip to content

Commit

Permalink
Some code improvements
Browse files Browse the repository at this point in the history
  • Loading branch information
hosseinmoein committed Feb 25, 2024
1 parent 494f8ec commit 48cc065
Show file tree
Hide file tree
Showing 4 changed files with 67 additions and 65 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
<img src="docs/Cougar.png" alt="Allocator Cougar" width="400" longdesc="https://htmlpreview.github.io/?https://github.com/hosseinmoein/Cougar/blob/master/README.md"/>

This repo includes several STL conformant allocators. There are two categories of allocators here:
1. Stack or Static based fixed size allocators. In this category you pre-allocate a fixed size of memory block either on the stack or statically. So you can have STL containers that are based on stack memory, for example. One of the side effects of these allocators is to overcome deficiencies in containers like <I>maps</I> and <I>lists</I> where their memory by default is not cache-friendly.
1. Stack or Static based fixed size allocators. In this category you pre-allocate a fixed size memory block either on the stack or statically. So you can have STL containers that are based on stack memory, for example. One of the side effects of these allocators is to overcome deficiencies in containers like <I>maps</I> and <I>lists</I> where their memory by default is not cache-friendly.
2. Custom Aligned allocator. This allocator allows you to allocate memory on a custom boundary. This way you can take advantage of SIMD instructions and techniques.

Please see the [tester file](test/allocator_tester.cc) for code samples.
Expand Down
35 changes: 17 additions & 18 deletions include/Cougar/AlignedAllocator.h
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,8 @@ class AlignedAllocator : public AlignedValue<T, AS> {
[[nodiscard]] inline constexpr const_pointer
address(const_reference cr) const { return (std::addressof(cr)); }

[[nodiscard]] constexpr size_type max_size() const {
[[nodiscard]] constexpr size_type
max_size() const {

return (std::numeric_limits<size_type>::max() / sizeof(value_type));
}
Expand All @@ -118,31 +119,33 @@ class AlignedAllocator : public AlignedValue<T, AS> {

public:

inline void construct(pointer p, const_reference val) const {
inline void
construct(pointer p, const_reference val) const {

new (static_cast<void *>(p)) value_type(val);
}
inline void construct(pointer p) const {

new (static_cast<void *>(p)) value_type();
}
inline void
construct(pointer p) const { new (static_cast<void *>(p)) value_type(); }

template<typename U, typename ... Ts >
inline void construct(U *p, Ts && ... args) const {
inline void
construct(U *p, Ts && ... args) const {

new (static_cast<void *>(p)) U(std::forward<Ts>(args) ...);
}

inline void destroy(pointer p) const { p->~value_type(); }
inline void
destroy(pointer p) const { p->~value_type(); }

template<typename U>
inline void destroy(U *p) const { p->~U(); }
inline void
destroy(U *p) const { p->~U(); }

[[nodiscard]] inline pointer allocate(size_type n_items) const {
[[nodiscard]] inline pointer
allocate(size_type n_items) const {

if (n_items == 0) return (nullptr);
if (n_items > max_size())
throw std::bad_array_new_length();
if (n_items > max_size()) throw std::bad_array_new_length();

return(reinterpret_cast<pointer>(
::operator new[](n_items * sizeof(value_type),
Expand Down Expand Up @@ -172,14 +175,10 @@ class AlignedAllocator : public AlignedValue<T, AS> {
// ----------------------------------------------------------------------------

template<typename T, std::size_t A>
struct allocator_declare {
using type = AlignedAllocator<T, A>;
};
struct allocator_declare { using type = AlignedAllocator<T, A>; };

template<typename T>
struct allocator_declare<T, 0> {
using type = std::allocator<T>;
};
struct allocator_declare<T, 0> { using type = std::allocator<T>; };

} // namespace hmcgr

Expand Down
93 changes: 48 additions & 45 deletions include/Cougar/FixedSizeAllocator.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,16 +47,17 @@ namespace hmcgr
template<typename T, std::size_t MAX_SIZE>
struct StaticStorage {

using mem_type = unsigned char;
using value_type = T;
using size_type = std::size_t;

inline static constexpr size_type max_size =
inline static constexpr size_type memory_size =
MAX_SIZE * sizeof(value_type);

// Main allocation space
//
alignas(value_type[])
inline static unsigned char buffer_[max_size];
inline static mem_type buffer_[memory_size];

StaticStorage() = default;
StaticStorage(const StaticStorage &) = default;
Expand All @@ -69,34 +70,36 @@ struct StaticStorage {
template<typename T, std::size_t MAX_SIZE>
struct StackStorage {

using mem_type = unsigned char;
using value_type = T;
using size_type = std::size_t;

inline static constexpr size_type max_size =
inline static constexpr size_type memory_size =
MAX_SIZE * sizeof(value_type);

// Main allocation space
//
alignas(value_type[])
unsigned char buffer_[max_size];
mem_type buffer_[memory_size];

StackStorage() = default;
StackStorage(const StackStorage &that) {

std::memcpy(buffer_, that.buffer_, max_size);
std::memcpy(buffer_, that.buffer_, memory_size);
}
StackStorage(StackStorage &&that) {

std::memcpy(buffer_, that.buffer_, max_size);
std::memcpy(buffer_, that.buffer_, memory_size);
}
~StackStorage() = default;
};

// ----------------------------------------------------------------------------

struct BestFitMemoryBlock {
struct BestFitBlock {

using value_type = unsigned char *;
using mem_type = unsigned char;
using value_type = mem_type *;
using size_type = std::size_t;

value_type address { nullptr };
Expand All @@ -105,31 +108,28 @@ struct BestFitMemoryBlock {
inline value_type
get_end() const { return (address + size); }
inline value_type
get_start() const { return (address - size); }
get_start() const { return (address); }

// Hash function
//
inline size_type
operator() (const BestFitMemoryBlock &mb) const {
operator() (const BestFitBlock &mb) const {

return (std::hash<value_type>{ }(mb.address));
}

inline friend bool
operator < (const BestFitMemoryBlock &lhs,
const BestFitMemoryBlock &rhs) {
operator < (const BestFitBlock &lhs, const BestFitBlock &rhs) {

return (lhs.size < rhs.size);
}
inline friend bool
operator > (const BestFitMemoryBlock &lhs,
const BestFitMemoryBlock &rhs) {
operator > (const BestFitBlock &lhs, const BestFitBlock &rhs) {

return (lhs.size > rhs.size);
}
inline friend bool
operator == (const BestFitMemoryBlock &lhs,
const BestFitMemoryBlock &rhs) {
operator == (const BestFitBlock &lhs, const BestFitBlock &rhs) {

return (lhs.address == rhs.address);
}
Expand All @@ -142,15 +142,16 @@ struct BestFitAlgo : public S {

using Base = S;
using size_type = Base::size_type;
using pointer = unsigned char *;
using pointer = Base::mem_type *;

BestFitAlgo() : Base() {

free_blocks_start_.insert({ Base::buffer_, Base::max_size });
free_blocks_start_.insert({ Base::buffer_, Base::memory_size });
free_blocks_assist_.insert(
std::make_pair(Base::buffer_, free_blocks_start_.begin()));
free_blocks_end_.insert(std::make_pair(Base::buffer_ + Base::max_size,
Base::max_size));
free_blocks_end_.insert(
std::make_pair(Base::buffer_ + Base::memory_size,
Base::memory_size));
}
~BestFitAlgo() = default;

Expand Down Expand Up @@ -207,7 +208,7 @@ struct BestFitAlgo : public S {
if (tail_block != free_blocks_assist_.end()) {
const size_type new_len =
used_iter->size + tail_block->second->size;
const BestFitMemoryBlock to_insert { to_be_freed, new_len };
const BestFitBlock to_insert { to_be_freed, new_len };

free_blocks_start_.erase(tail_block->second);
free_blocks_assist_.erase(tail_block);
Expand Down Expand Up @@ -280,9 +281,8 @@ struct BestFitAlgo : public S {

// It is based on size, so it must be multi-set
//
using blk_set = std::multiset<BestFitMemoryBlock>;
using blk_uoset =
std::unordered_set<BestFitMemoryBlock, BestFitMemoryBlock>;
using blk_set = std::multiset<BestFitBlock>;
using blk_uoset = std::unordered_set<BestFitBlock, BestFitBlock>;
using blk_uomap = std::unordered_map<pointer, std::size_t>;
using blk_assist = std::unordered_map<pointer, blk_set::const_iterator>;

Expand All @@ -309,27 +309,27 @@ struct BestFitAlgo : public S {
// ----------------------------------------------------------------------------

template<typename T, std::size_t MAX_SIZE>
struct FirstFitStaticBase : public StaticStorage<T, MAX_SIZE> {
struct FirstFitStatic : public StaticStorage<T, MAX_SIZE> {

using Base = StaticStorage<T, MAX_SIZE>;
using value_type = Base::value_type;
using size_type = Base::size_type;

inline static constexpr unsigned char FREE_ { 0 };
inline static constexpr unsigned char USED_ { 1 };
inline static constexpr std::size_t max_size = MAX_SIZE;
inline static constexpr std::size_t memory_size = MAX_SIZE;

// The bitmap to indicate which slots are in use.
//
alignas(value_type[])
inline static unsigned char in_use_[max_size];
inline static unsigned char in_use_[memory_size];

// Pointer to the first free slot.
//
alignas(value_type *)
inline static unsigned char *first_free_ptr_ { in_use_ };

FirstFitStaticBase() : Base() {
FirstFitStatic() : Base() {

// This is guaranteed to execute only once even in multithreading
//
Expand All @@ -338,44 +338,44 @@ struct FirstFitStaticBase : public StaticStorage<T, MAX_SIZE> {
return (0);
});
}
FirstFitStaticBase(const FirstFitStaticBase &that) = default;
FirstFitStaticBase(FirstFitStaticBase &&that) = default;
~FirstFitStaticBase() = default;
FirstFitStatic(const FirstFitStatic &that) = default;
FirstFitStatic(FirstFitStatic &&that) = default;
~FirstFitStatic() = default;
};

// ----------------------------------------------------------------------------

template<typename T, std::size_t MAX_SIZE>
struct FirstFitStackBase : public StackStorage<T, MAX_SIZE> {
struct FirstFitStack : public StackStorage<T, MAX_SIZE> {

using Base = StackStorage<T, MAX_SIZE>;
using value_type = Base::value_type;
using size_type = Base::size_type;

inline static constexpr unsigned char FREE_ { 0 };
inline static constexpr unsigned char USED_ { 1 };
inline static constexpr std::size_t max_size = MAX_SIZE;
inline static constexpr std::size_t memory_size = MAX_SIZE;

// The bitmap to indicate which slots are in use.
//
alignas(value_type[])
unsigned char in_use_[max_size];
unsigned char in_use_[memory_size];

// Pointer to the first free slot.
//
alignas(value_type *)
unsigned char *first_free_ptr_ { in_use_ };

FirstFitStackBase() : Base() { std::memset(in_use_, FREE_, MAX_SIZE); }
FirstFitStackBase(const FirstFitStackBase &that) : Base(that) {
FirstFitStack() : Base() { std::memset(in_use_, FREE_, MAX_SIZE); }
FirstFitStack(const FirstFitStack &that) : Base(that) {

std::memset(in_use_, FREE_, MAX_SIZE);
}
FirstFitStackBase(FirstFitStackBase &&that) : Base(that) {
FirstFitStack(FirstFitStack &&that) : Base(that) {

std::memset(in_use_, FREE_, MAX_SIZE);
}
~FirstFitStackBase() = default;
~FirstFitStack() = default;
};

// ----------------------------------------------------------------------------
Expand All @@ -386,7 +386,7 @@ struct FirstFitAlgo : public S {
using Base = S;
using value_type = Base::value_type;
using size_type = Base::size_type;
using pointer = unsigned char *;
using pointer = Base::mem_type *;

FirstFitAlgo() = default;
~FirstFitAlgo() = default;
Expand All @@ -399,7 +399,7 @@ struct FirstFitAlgo : public S {
// Pointers to the "in use" bitmap.
//
unsigned char *first_ptr = Base::first_free_ptr_;
unsigned char *const end_ptr = &(Base::in_use_[Base::max_size]);
unsigned char *const end_ptr = &(Base::in_use_[Base::memory_size]);
const size_type n_items = requested_size / sizeof(value_type);

// Find first fit allocation algorithm, starting from the first
Expand Down Expand Up @@ -480,9 +480,9 @@ class FixedSizeAllocator : private ALGO<STORAGE<T, MAX_SIZE>> {
using propagate_on_container_swap = std::false_type;
using is_always_equal = std::true_type;

// This is only necessary because allocator has a second and third template
// arguments for the alignment that will make the default
// std::allocator_traits implementation fail during compilation.
// This is only necessary because allocator has a second, third and fourth
// template arguments that will make the default std::allocator_traits
// implementation fail during compilation.
//
template<typename U>
struct rebind {
Expand Down Expand Up @@ -542,6 +542,9 @@ class FixedSizeAllocator : private ALGO<STORAGE<T, MAX_SIZE>> {
[[nodiscard]] inline pointer
allocate(size_type n_items, [[maybe_unused]] const_pointer cp) {

if (n_items == 0) return (nullptr);
if (n_items > max_size()) throw std::bad_array_new_length();

auto memory_ptr = this->get_space(n_items * sizeof(value_type));

return (reinterpret_cast<pointer>(memory_ptr));
Expand Down Expand Up @@ -576,13 +579,13 @@ using StackBestFitAllocator =
//
template<typename T, std::size_t MAX_SIZE>
using StaticFirstFitAllocator =
FixedSizeAllocator<T, MAX_SIZE, FirstFitStaticBase, FirstFitAlgo>;
FixedSizeAllocator<T, MAX_SIZE, FirstFitStatic, FirstFitAlgo>;

// This is faster than best-fit, but it causes more fragmentations.
//
template<typename T, std::size_t MAX_SIZE>
using StackFirstFitAllocator =
FixedSizeAllocator<T, MAX_SIZE, FirstFitStackBase, FirstFitAlgo>;
FixedSizeAllocator<T, MAX_SIZE, FirstFitStack, FirstFitAlgo>;

} // namespace hmcgr

Expand Down
2 changes: 1 addition & 1 deletion test/allocator_tester.cc
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,7 @@ static void test_first_fit_static_allocator() {

// Test how badly it fragments the memory
//
FirstFitAlgo<FirstFitStaticBase<int, 10000>> allocator;
FirstFitAlgo<FirstFitStatic<int, 10000>> allocator;
std::vector<std::pair<unsigned char *, std::size_t>> ptr_vec;
std::mt19937 gen { 98 };

Expand Down

0 comments on commit 48cc065

Please sign in to comment.