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
23
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
56
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
657
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.
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: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.