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 Commissariat à l'énergie atomique et aux énergies alternatives (CEA)
2// Copyright (c) 2018 Centre national de la recherche scientifique (CNRS)
3// Copyright (c) 2018-2024 Simons Foundation
4//
5// Licensed under the Apache License, Version 2.0 (the "License");
6// you may not use this file except in compliance with the License.
7// You may obtain a copy of the License at
8//
9// http://www.apache.org/licenses/LICENSE-2.0.txt
10//
11// Unless required by applicable law or agreed to in writing, software
12// distributed under the License is distributed on an "AS IS" BASIS,
13// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14// See the License for the specific language governing permissions and
15// limitations under the License.
16//
17// Authors: Thomas Hahn, Olivier Parcollet, Nils Wentzell
18
24#pragma once
25
26#include "./address_space.hpp"
27#include "./malloc.hpp"
28#include "./memset.hpp"
29#include "../macros.hpp"
30
31#include <algorithm>
32#include <cstddef>
33#include <cstdint>
34#include <cstdlib>
35#include <memory>
36#include <vector>
37#include <utility>
38
39#ifndef NDEBUG
40#include <iostream>
41#endif
42
43#if defined(__has_feature)
44#if __has_feature(address_sanitizer)
45#include <sanitizer/asan_interface.h>
46#define NDA_USE_ASAN
47#endif
48#endif
49
50namespace nda::mem {
51
58 struct blk_t {
60 char *ptr = nullptr;
61
63 size_t s = 0;
64 };
65
70 template <AddressSpace AdrSp = Host>
71 class mallocator {
72 public:
74 mallocator() = default;
75
77 mallocator(mallocator const &) = delete;
78
80 mallocator(mallocator &&) = default;
81
83 mallocator &operator=(mallocator const &) = delete;
84
87
89 static constexpr auto address_space = AdrSp;
90
97 static blk_t allocate(size_t s) noexcept { return {(char *)malloc<AdrSp>(s), s}; }
98
109 static blk_t allocate_zero(size_t s) noexcept {
110 if constexpr (AdrSp == mem::Host) {
111 return {(char *)std::calloc(s, 1 /* byte */), s}; // NOLINT (C-style cast is fine here)
112 } else {
113 char *ptr = (char *)malloc<AdrSp>(s);
114 memset<AdrSp>(ptr, 0, s);
115 return {ptr, s};
116 }
117 }
118
123 static void deallocate(blk_t b) noexcept { free<AdrSp>((void *)b.ptr); }
124 };
125
136 template <int ChunkSize>
137 class bucket {
138 // Unique pointer to handle memory allocation for the bucket.
139 std::unique_ptr<char[]> _start = std::make_unique<char[]>(TotalChunkSize); // NOLINT (C-style array is fine here)
140
141 // Pointer to the start of the bucket.
142 char *p = _start.get();
143
144 // Bitmask to keep track of which chunks are free.
145 uint64_t flags = uint64_t(-1);
146
147 public:
149 static constexpr int TotalChunkSize = 64 * ChunkSize;
150
152 static constexpr auto address_space = Host;
153
154#ifdef NDA_USE_ASAN
155 bucket() { __asan_poison_memory_region(p, TotalChunkSize); }
156 ~bucket() { __asan_unpoison_memory_region(p, TotalChunkSize); }
157#else
159 bucket() = default;
160#endif
162 bucket(bucket const &) = delete;
163
165 bucket(bucket &&) = default;
166
168 bucket &operator=(bucket const &) = delete;
169
171 bucket &operator=(bucket &&) = default;
172
179 blk_t allocate(size_t s) noexcept {
180 // check the size and if there is a free chunk, otherwise abort
181 if (s > ChunkSize) std::abort();
182 if (flags == 0) std::abort();
183
184 // find the first free chunk
185 int pos = __builtin_ctzll(flags);
186
187 // update the bitmask and return the memory block
188 flags &= ~(1ull << pos);
189 blk_t b{p + static_cast<ptrdiff_t>(pos * ChunkSize), s};
190#ifdef NDA_USE_ASAN
191 __asan_unpoison_memory_region(b.ptr, ChunkSize);
192#endif
193 return b;
194 }
195
202 blk_t allocate_zero(size_t s) noexcept {
203 auto blk = allocate(s);
204 std::memset(blk.ptr, 0, s);
205 return blk;
206 }
207
212 void deallocate(blk_t b) noexcept {
213#ifdef NDA_USE_ASAN
214 __asan_poison_memory_region(b.ptr, ChunkSize);
215#endif
216 int pos = (b.ptr - p) / ChunkSize;
217 flags |= (1ull << pos);
218 }
219
224 [[nodiscard]] bool is_full() const noexcept { return flags == 0; }
225
230 [[nodiscard]] bool empty() const noexcept { return flags == uint64_t(-1); }
231
236 [[nodiscard]] const char *data() const noexcept { return p; }
237
242 [[nodiscard]] auto mask() const noexcept { return flags; }
243
250 [[nodiscard]] bool owns(blk_t b) const noexcept { return b.ptr >= p and b.ptr < p + TotalChunkSize; }
251 };
252
263 template <int ChunkSize>
265 // Alias for the bucket allocator type.
266 using b_t = bucket<ChunkSize>;
267
268 // Vector of nda::mem::bucket allocators (ordered in memory).
269 std::vector<b_t> bu_vec;
270
271 // Iterator to the current bucket in use.
272 typename std::vector<b_t>::iterator bu;
273
274 // Find a bucket with a free chunk, otherwise create a new one and insert it at the correct position.
275 [[gnu::noinline]] void find_non_full_bucket() {
276 bu = std::find_if(bu_vec.begin(), bu_vec.end(), [](auto const &b) { return !b.is_full(); });
277 if (bu != bu_vec.end()) return;
278 b_t b;
279 auto pos = std::upper_bound(bu_vec.begin(), bu_vec.end(), b, [](auto const &b1, auto const &b2) { return b1.data() < b2.data(); });
280 bu = bu_vec.insert(pos, std::move(b));
281 }
282
283 public:
285 static constexpr auto address_space = Host;
286
288 multi_bucket() : bu_vec(1), bu(bu_vec.begin()) {}
289
291 multi_bucket(multi_bucket const &) = delete;
292
295
298
301
308 blk_t allocate(size_t s) noexcept {
309 if ((bu == bu_vec.end()) or (bu->is_full())) find_non_full_bucket();
310 return bu->allocate(s);
311 }
312
320 blk_t allocate_zero(size_t s) noexcept {
321 auto blk = allocate(s);
322 std::memset(blk.ptr, 0, s);
323 return blk;
324 }
325
334 void deallocate(blk_t b) noexcept {
335 // try the current bucket first
336 if (bu != bu_vec.end() and bu->owns(b)) {
337 bu->deallocate(b);
338 return;
339 }
340
341 // otherwise, find the owning bucket in the vector and deallocate
342 bu = std::lower_bound(bu_vec.begin(), bu_vec.end(), b.ptr, [](auto const &b1, auto p) { return b1.data() <= p; });
343 --bu;
344 EXPECTS_WITH_MESSAGE((bu != bu_vec.end()), "Error in nda::mem::multi_bucket::deallocate: Owning bucket not found");
345 EXPECTS_WITH_MESSAGE((bu->owns(b)), "Error in nda::mem::multi_bucket::deallocate: Owning bucket not found");
346 bu->deallocate(b);
347
348 // remove bucket the current bucket if it is empty and not the only one
349 if (!bu->empty()) return;
350 if (bu_vec.size() <= 1) return;
351 bu_vec.erase(bu);
352 bu = bu_vec.end();
353 }
354
359 [[nodiscard]] bool empty() const noexcept { return bu_vec.size() == 1 && bu_vec[0].empty(); }
360
365 [[nodiscard]] auto const &buckets() const noexcept { return bu_vec; }
366
373 [[nodiscard]] bool owns(blk_t b) const noexcept {
374 bool res = false;
375 for (const auto &mb : bu_vec) { res = res || mb.owns(b); }
376 return res;
377 }
378 };
379
390 template <size_t Threshold, Allocator A, Allocator B>
392 // Allocator for small memory blocks.
393 A small;
394
395 // Allocator for big memory blocks.
396 B big;
397
398 public:
399 static_assert(A::address_space == B::address_space);
400
402 static constexpr auto address_space = A::address_space;
403
405 segregator() = default;
406
408 segregator(segregator const &) = delete;
409
411 segregator(segregator &&) = default;
412
414 segregator &operator=(segregator const &) = delete;
415
418
426 blk_t allocate(size_t s) noexcept { return s <= Threshold ? small.allocate(s) : big.allocate(s); }
427
435 blk_t allocate_zero(size_t s) noexcept { return s <= Threshold ? small.allocate_zero(s) : big.allocate_zero(s); }
436
443 void deallocate(blk_t b) noexcept { return b.s <= Threshold ? small.deallocate(b) : big.deallocate(b); }
444
451 [[nodiscard]] bool owns(blk_t b) const noexcept { return small.owns(b) or big.owns(b); }
452 };
453
463 template <Allocator A>
464 class leak_check : A {
465 // Total memory used by the allocator.
466 long memory_used = 0;
467
468 public:
470 static constexpr auto address_space = A::address_space;
471
473 leak_check() = default;
474
476 leak_check(leak_check const &) = delete;
477
479 leak_check(leak_check &&) = default;
480
482 leak_check &operator=(leak_check const &) = delete;
483
486
492 if (!empty()) {
493#ifndef NDEBUG
494 std::cerr << "Memory leak in allocator: " << memory_used << " bytes leaked\n";
495 std::abort();
496#endif
497 }
498 }
499
506 blk_t allocate(size_t s) {
507 blk_t b = A::allocate(s);
508 memory_used += b.s;
509 return b;
510 }
511
519 blk_t b = A::allocate_zero(s);
520 memory_used += b.s;
521 return b;
522 }
523
529 void deallocate(blk_t b) noexcept {
530 memory_used -= b.s;
531 if (memory_used < 0) {
532#ifndef NDEBUG
533 std::cerr << "Memory used by allocator < 0: Memory block to be deleted: b.s = " << b.s << ", b.ptr = " << (void *)b.ptr << "\n";
534 std::abort();
535#endif
536 }
537 A::deallocate(b);
538 }
539
544 [[nodiscard]] bool empty() const { return (memory_used == 0); }
545
552 [[nodiscard]] bool owns(blk_t b) const noexcept { return A::owns(b); }
553
558 [[nodiscard]] long get_memory_used() const noexcept { return memory_used; }
559 };
560
570 template <Allocator A>
571 class stats : A {
572 // Histogram of the allocation sizes.
573 std::vector<uint64_t> hist = std::vector<uint64_t>(65, 0);
574
575 public:
577 static constexpr auto address_space = A::address_space;
578
580 stats() = default;
581
583 stats(stats const &) = delete;
584
586 stats(stats &&) = default;
587
589 stats &operator=(stats const &) = delete;
590
592 stats &operator=(stats &&) = default;
593
596#ifndef NDEBUG
597 print_histogram(std::cerr);
598#endif
599 }
600
607 blk_t allocate(uint64_t s) {
608 // __builtin_clzl returns the number of leading zeros
609 ++hist[__builtin_clzl(s)];
610 return A::allocate(s);
611 }
612
619 blk_t allocate_zero(uint64_t s) {
620 // __builtin_clzl returns the number of leading zeros
621 ++hist[__builtin_clzl(s)];
622 return A::allocate_zero(s);
623 }
624
629 void deallocate(blk_t b) noexcept { A::deallocate(b); }
630
637 [[nodiscard]] bool owns(blk_t b) const noexcept { return A::owns(b); }
638
643 [[nodiscard]] auto const &histogram() const noexcept { return hist; }
644
649 void print_histogram(std::ostream &os) const {
650 os << "Allocation size histogram :\n";
651 os << "[0, 2^0): " << hist.back() << "\n";
652 for (int i = 0; i < 64; ++i) { os << "[2^" << i << ", 2^" << i + 1 << "): " << hist[63 - i] << "\n"; }
653 }
654 };
655
658} // 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.
Wrap an allocator to check for memory leaks.
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.
Custom allocator that uses nda::mem::malloc to allocate memory.
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.
Custom allocator that uses multiple nda::mem::bucket allocators.
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.
Custom allocator that dispatches memory allocation to one of two allocators based on the size of the ...
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,...
Wrap an allocator to gather statistics about memory allocation.
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:49
void memset(void *p, int value, size_t count)
Call the correct memset function based on the given address space.
Definition memset.hpp:49
void free(void *p)
Call the correct free function based on the given address space.
Definition malloc.hpp:75
Macros used in the nda library.
Provides a generic malloc and free 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.