TRIQS/nda 1.3.0
Multi-dimensional array library for C++
Loading...
Searching...
No Matches
allocators.hpp
Go to the documentation of this file.
1// Copyright (c) 2018--present, The Simons Foundation
2// This file is part of TRIQS/nda and is licensed under the Apache License, Version 2.0.
3// SPDX-License-Identifier: Apache-2.0
4// See LICENSE in the root of this distribution for details.
5
10
11#pragma once
12
13#include "./address_space.hpp"
14#include "./malloc.hpp"
15#include "./memset.hpp"
16#include "./memcpy.hpp"
17#include "./fill.hpp"
18#include "../macros.hpp"
19
20#include <algorithm>
21#include <cstddef>
22#include <cstdint>
23#include <cstdlib>
24#include <memory>
25#include <vector>
26#include <utility>
27
28#ifndef NDEBUG
29#include <iostream>
30#endif
31
32#if defined(__has_feature)
33#if __has_feature(address_sanitizer)
34#include <sanitizer/asan_interface.h>
35#define NDA_USE_ASAN
36#endif
37#endif
38
39namespace nda::mem {
40
45
47 struct blk_t {
49 char *ptr = nullptr;
50
52 size_t s = 0;
53 };
54
59 template <AddressSpace AdrSp = Host>
60 class mallocator {
61 public:
63 mallocator() = default;
64
66 mallocator(mallocator const &) = delete;
67
69 mallocator(mallocator &&) = default;
70
72 mallocator &operator=(mallocator const &) = delete;
73
76
78 static constexpr auto address_space = AdrSp;
79
86 static blk_t allocate(size_t s) noexcept { return {(char *)malloc<AdrSp>(s), s}; }
87
98 static blk_t allocate_zero(size_t s) noexcept {
99 if constexpr (AdrSp == mem::Host) {
100 return {(char *)std::calloc(s, 1 /* byte */), s}; // NOLINT (C-style cast is fine here)
101 } else {
102 char *ptr = (char *)malloc<AdrSp>(s);
103 memset<AdrSp>(ptr, 0, s);
104 return {ptr, s};
105 }
106 }
107
112 static void deallocate(blk_t b) noexcept { free<AdrSp>((void *)b.ptr); }
113 };
114
125 template <int ChunkSize>
126 class bucket {
127 // Unique pointer to handle memory allocation for the bucket.
128 std::unique_ptr<char[]> _start = std::make_unique<char[]>(TotalChunkSize); // NOLINT (C-style array is fine here)
129
130 // Pointer to the start of the bucket.
131 char *p = _start.get();
132
133 // Bitmask to keep track of which chunks are free.
134 uint64_t flags = uint64_t(-1);
135
136 public:
138 static constexpr int TotalChunkSize = 64 * ChunkSize;
139
141 static constexpr auto address_space = Host;
142
143#ifdef NDA_USE_ASAN
144 bucket() { __asan_poison_memory_region(p, TotalChunkSize); }
145 ~bucket() { __asan_unpoison_memory_region(p, TotalChunkSize); }
146#else
148 bucket() = default;
149#endif
151 bucket(bucket const &) = delete;
152
154 bucket(bucket &&) = default;
155
157 bucket &operator=(bucket const &) = delete;
158
160 bucket &operator=(bucket &&) = default;
161
168 blk_t allocate(size_t s) noexcept {
169 // check the size and if there is a free chunk, otherwise abort
170 if (s > ChunkSize) std::abort();
171 if (flags == 0) std::abort();
172
173 // find the first free chunk
174 int pos = __builtin_ctzll(flags);
175
176 // update the bitmask and return the memory block
177 flags &= ~(1ull << pos);
178 blk_t b{p + static_cast<ptrdiff_t>(pos * ChunkSize), s};
179#ifdef NDA_USE_ASAN
180 __asan_unpoison_memory_region(b.ptr, ChunkSize);
181#endif
182 return b;
183 }
184
191 blk_t allocate_zero(size_t s) noexcept {
192 auto blk = allocate(s);
193 std::memset(blk.ptr, 0, s);
194 return blk;
195 }
196
201 void deallocate(blk_t b) noexcept {
202#ifdef NDA_USE_ASAN
203 __asan_poison_memory_region(b.ptr, ChunkSize);
204#endif
205 int pos = (b.ptr - p) / ChunkSize;
206 flags |= (1ull << pos);
207 }
208
213 [[nodiscard]] bool is_full() const noexcept { return flags == 0; }
214
219 [[nodiscard]] bool empty() const noexcept { return flags == uint64_t(-1); }
220
225 [[nodiscard]] const char *data() const noexcept { return p; }
226
231 [[nodiscard]] auto mask() const noexcept { return flags; }
232
239 [[nodiscard]] bool owns(blk_t b) const noexcept { return b.ptr >= p and b.ptr < p + TotalChunkSize; }
240 };
241
252 template <int ChunkSize>
254 // Alias for the bucket allocator type.
255 using b_t = bucket<ChunkSize>;
256
257 // Vector of nda::mem::bucket allocators (ordered in memory).
258 std::vector<b_t> bu_vec;
259
260 // Iterator to the current bucket in use.
261 typename std::vector<b_t>::iterator bu;
262
263 // Find a bucket with a free chunk, otherwise create a new one and insert it at the correct position.
264 [[gnu::noinline]] void find_non_full_bucket() {
265 bu = std::find_if(bu_vec.begin(), bu_vec.end(), [](auto const &b) { return !b.is_full(); });
266 if (bu != bu_vec.end()) return;
267 b_t b;
268 auto pos = std::upper_bound(bu_vec.begin(), bu_vec.end(), b, [](auto const &b1, auto const &b2) { return b1.data() < b2.data(); });
269 bu = bu_vec.insert(pos, std::move(b));
270 }
271
272 public:
274 static constexpr auto address_space = Host;
275
277 multi_bucket() : bu_vec(1), bu(bu_vec.begin()) {}
278
280 multi_bucket(multi_bucket const &) = delete;
281
284
287
290
297 blk_t allocate(size_t s) noexcept {
298 if ((bu == bu_vec.end()) or (bu->is_full())) find_non_full_bucket();
299 return bu->allocate(s);
300 }
301
309 blk_t allocate_zero(size_t s) noexcept {
310 auto blk = allocate(s);
311 std::memset(blk.ptr, 0, s);
312 return blk;
313 }
314
323 void deallocate(blk_t b) noexcept {
324 // try the current bucket first
325 if (bu != bu_vec.end() and bu->owns(b)) {
326 bu->deallocate(b);
327 return;
328 }
329
330 // otherwise, find the owning bucket in the vector and deallocate
331 bu = std::lower_bound(bu_vec.begin(), bu_vec.end(), b.ptr, [](auto const &b1, auto p) { return b1.data() <= p; });
332 --bu;
333 EXPECTS_WITH_MESSAGE((bu != bu_vec.end()), "Error in nda::mem::multi_bucket::deallocate: Owning bucket not found");
334 EXPECTS_WITH_MESSAGE((bu->owns(b)), "Error in nda::mem::multi_bucket::deallocate: Owning bucket not found");
335 bu->deallocate(b);
336
337 // remove bucket the current bucket if it is empty and not the only one
338 if (!bu->empty()) return;
339 if (bu_vec.size() <= 1) return;
340 bu_vec.erase(bu);
341 bu = bu_vec.end();
342 }
343
348 [[nodiscard]] bool empty() const noexcept { return bu_vec.size() == 1 && bu_vec[0].empty(); }
349
354 [[nodiscard]] auto const &buckets() const noexcept { return bu_vec; }
355
362 [[nodiscard]] bool owns(blk_t b) const noexcept {
363 bool res = false;
364 for (const auto &mb : bu_vec) { res = res || mb.owns(b); }
365 return res;
366 }
367 };
368
379 template <size_t Threshold, Allocator A, Allocator B>
381 // Allocator for small memory blocks.
382 A small;
383
384 // Allocator for big memory blocks.
385 B big;
386
387 public:
388 static_assert(A::address_space == B::address_space);
389
391 static constexpr auto address_space = A::address_space;
392
394 segregator() = default;
395
397 segregator(segregator const &) = delete;
398
400 segregator(segregator &&) = default;
401
403 segregator &operator=(segregator const &) = delete;
404
407
415 blk_t allocate(size_t s) noexcept { return s <= Threshold ? small.allocate(s) : big.allocate(s); }
416
424 blk_t allocate_zero(size_t s) noexcept { return s <= Threshold ? small.allocate_zero(s) : big.allocate_zero(s); }
425
432 void deallocate(blk_t b) noexcept { return b.s <= Threshold ? small.deallocate(b) : big.deallocate(b); }
433
440 [[nodiscard]] bool owns(blk_t b) const noexcept { return small.owns(b) or big.owns(b); }
441 };
442
452 template <Allocator A>
453 class leak_check : A {
454 // Total memory used by the allocator.
455 long memory_used = 0;
456
457 public:
459 static constexpr auto address_space = A::address_space;
460
462 leak_check() = default;
463
465 leak_check(leak_check const &) = delete;
466
468 leak_check(leak_check &&) = default;
469
471 leak_check &operator=(leak_check const &) = delete;
472
475
481 if (!empty()) {
482#ifndef NDEBUG
483 std::cerr << "Memory leak in allocator: " << memory_used << " bytes leaked\n";
484 std::abort();
485#endif
486 }
487 }
488
495 blk_t allocate(size_t s) {
496 blk_t b = A::allocate(s);
497 memory_used += b.s;
498 return b;
499 }
500
508 blk_t b = A::allocate_zero(s);
509 memory_used += b.s;
510 return b;
511 }
512
518 void deallocate(blk_t b) noexcept {
519 memory_used -= b.s;
520 if (memory_used < 0) {
521#ifndef NDEBUG
522 std::cerr << "Memory used by allocator < 0: Memory block to be deleted: b.s = " << b.s << ", b.ptr = " << (void *)b.ptr << "\n";
523 std::abort();
524#endif
525 }
526 A::deallocate(b);
527 }
528
533 [[nodiscard]] bool empty() const { return (memory_used == 0); }
534
541 [[nodiscard]] bool owns(blk_t b) const noexcept { return A::owns(b); }
542
547 [[nodiscard]] long get_memory_used() const noexcept { return memory_used; }
548 };
549
559 template <Allocator A>
560 class stats : A {
561 // Histogram of the allocation sizes.
562 std::vector<uint64_t> hist = std::vector<uint64_t>(65, 0);
563
564 public:
566 static constexpr auto address_space = A::address_space;
567
569 stats() = default;
570
572 stats(stats const &) = delete;
573
575 stats(stats &&) = default;
576
578 stats &operator=(stats const &) = delete;
579
581 stats &operator=(stats &&) = default;
582
585#ifndef NDEBUG
586 print_histogram(std::cerr);
587#endif
588 }
589
596 blk_t allocate(uint64_t s) {
597 // __builtin_clzl returns the number of leading zeros
598 ++hist[__builtin_clzl(s)];
599 return A::allocate(s);
600 }
601
608 blk_t allocate_zero(uint64_t s) {
609 // __builtin_clzl returns the number of leading zeros
610 ++hist[__builtin_clzl(s)];
611 return A::allocate_zero(s);
612 }
613
618 void deallocate(blk_t b) noexcept { A::deallocate(b); }
619
626 [[nodiscard]] bool owns(blk_t b) const noexcept { return A::owns(b); }
627
632 [[nodiscard]] auto const &histogram() const noexcept { return hist; }
633
638 void print_histogram(std::ostream &os) const {
639 os << "Allocation size histogram :\n";
640 os << "[0, 2^0): " << hist.back() << "\n";
641 for (int i = 0; i < 64; ++i) { os << "[2^" << i << ", 2^" << i + 1 << "): " << hist[63 - i] << "\n"; }
642 }
643 };
644
646
647} // namespace nda::mem
Provides definitions and type traits involving the different memory address spaces supported by nda.
Custom allocator that allocates a bucket of memory on the heap consisting of 64 chunks.
bucket(bucket &&)=default
Default move constructor.
bool empty() const noexcept
Check if the bucket is empty.
bucket()=default
Default constructor.
bool owns(blk_t b) const noexcept
Check if a given nda::mem::blk_t memory block is owned by the bucket.
void deallocate(blk_t b) noexcept
Deallocate a chunk of memory from the bucket by simply resetting the bitmask.
bool is_full() const noexcept
Check if the bucket is full.
auto mask() const noexcept
Get the bitmask of the bucket.
const char * data() const noexcept
Get a pointer to the start of the bucket.
static constexpr auto address_space
Only Host nda::mem::AddressSpace is supported for this allocator.
bucket(bucket const &)=delete
Deleted copy constructor.
bucket & operator=(bucket const &)=delete
Deleted copy assignment operator.
bucket & operator=(bucket &&)=default
Default move assignment operator.
blk_t allocate(size_t s) noexcept
Allocate a chunk of memory in the bucket and update the bitmask.
static constexpr int TotalChunkSize
Total size of the bucket in bytes.
blk_t allocate_zero(size_t s) noexcept
Allocate a chunk of memory in the bucket, set it to zero and update the bitmask.
bool empty() const
Check if the base allocator is empty.
long get_memory_used() const noexcept
Get the total memory used by the base allocator.
bool owns(blk_t b) const noexcept
Check if a given nda::mem::blk_t memory block is owned by the base allocator.
leak_check & operator=(leak_check &&)=default
Default move assignment operator.
static constexpr auto address_space
nda::mem::AddressSpace in which the memory is allocated.
leak_check(leak_check const &)=delete
Deleted copy constructor.
~leak_check()
Destructor that checks for memory leaks.
blk_t allocate_zero(size_t s)
Allocate memory, set it to zero and update the total memory used.
leak_check()=default
Default constructor.
blk_t allocate(size_t s)
Allocate memory and update the total memory used.
leak_check & operator=(leak_check const &)=delete
Deleted copy assignment operator.
void deallocate(blk_t b) noexcept
Deallocate memory and update the total memory used.
leak_check(leak_check &&)=default
Default move constructor.
mallocator & operator=(mallocator const &)=delete
Deleted copy assignment operator.
static void deallocate(blk_t b) noexcept
Deallocate memory using nda::mem::free.
mallocator & operator=(mallocator &&)=default
Default move assignment operator.
mallocator()=default
Default constructor.
mallocator(mallocator const &)=delete
Deleted copy constructor.
static blk_t allocate_zero(size_t s) noexcept
Allocate memory and set it to zero.
static blk_t allocate(size_t s) noexcept
Allocate memory using nda::mem::malloc.
static constexpr auto address_space
nda::mem::AddressSpace in which the memory is allocated.
mallocator(mallocator &&)=default
Default move constructor.
multi_bucket(multi_bucket &&)=default
Default move constructor.
bool owns(blk_t b) const noexcept
Check if a given nda::mem::blk_t memory block is owned by allocator.
blk_t allocate_zero(size_t s) noexcept
Allocate a chunk of memory in the current bucket or find a new one if the current one is full and set...
void deallocate(blk_t b) noexcept
Deallocate a chunk of memory from the bucket to which it belongs.
multi_bucket()
Default constructor.
bool empty() const noexcept
Check if the current allocator is empty.
static constexpr auto address_space
Only Host nda::mem::AddressSpace is supported for this allocator.
multi_bucket & operator=(multi_bucket &&)=default
Default move assignment operator.
auto const & buckets() const noexcept
Get the bucket vector.
blk_t allocate(size_t s) noexcept
Allocate a chunk of memory in the current bucket or find a new one if the current one is full.
multi_bucket(multi_bucket const &)=delete
Deleted copy constructor.
multi_bucket & operator=(multi_bucket const &)=delete
Deleted copy assignment operator.
blk_t allocate_zero(size_t s) noexcept
Allocate memory and set the memory to zero using the small allocator if the size is less than or equa...
segregator()=default
Default constructor.
segregator(segregator const &)=delete
Deleted copy constructor.
void deallocate(blk_t b) noexcept
Deallocate memory using the small allocator if the size is less than or equal to the Threshold,...
bool owns(blk_t b) const noexcept
Check if a given nda::mem::blk_t memory block is owned by the allocator.
segregator & operator=(segregator &&)=default
Default move assignment operator.
segregator(segregator &&)=default
Default move constructor.
static constexpr auto address_space
nda::mem::AddressSpace in which the memory is allocated.
segregator & operator=(segregator const &)=delete
Deleted copy assignment operator.
blk_t allocate(size_t s) noexcept
Allocate memory using the small allocator if the size is less than or equal to the Threshold,...
stats & operator=(stats &&)=default
Default move assignment operator.
static constexpr auto address_space
nda::mem::AddressSpace in which the memory is allocated.
blk_t allocate(uint64_t s)
Allocate memory and update the histogram.
auto const & histogram() const noexcept
Get the histogram of the allocation sizes.
void print_histogram(std::ostream &os) const
Print the histogram to a std::ostream.
blk_t allocate_zero(uint64_t s)
Allocate memory, set it to zero and update the histogram.
~stats()
Destructor that outputs the statistics about the memory allocation in debug mode.
stats & operator=(stats const &)=delete
Deleted copy assignment operator.
bool owns(blk_t b) const noexcept
Check if a given nda::mem::blk_t memory block is owned by the base allocator.
stats(stats const &)=delete
Deleted copy constructor.
void deallocate(blk_t b) noexcept
Deallocate memory.
stats(stats &&)=default
Default move constructor.
stats()=default
Default constructor.
void * malloc(size_t size)
Call the correct malloc function based on the given address space.
Definition malloc.hpp:38
void memset(void *p, int value, size_t count)
Call the correct memset function based on the given address space.
Definition memset.hpp:38
void free(void *p)
Call the correct free function based on the given address space.
Definition malloc.hpp:64
Macros used in the nda library.
Provides a generic malloc and free function for different address spaces.
Provides a generic memcpy and memcpy2D function for different address spaces.
Provides a generic memset and memset2D function for different address spaces.
Memory block consisting of a pointer and its size.
char * ptr
Pointer to the memory block.
size_t s
Size of the memory block in bytes.