diff --git a/src/mem/largealloc.h b/src/mem/largealloc.h index 114fa39..2a0c7b3 100644 --- a/src/mem/largealloc.h +++ b/src/mem/largealloc.h @@ -70,7 +70,17 @@ namespace snmalloc */ AddressSpaceManager address_space = {}; + /** + * High-water mark of used memory. + */ + std::atomic peak_memory_used_bytes{0}; + public: + /** + * Memory current available in large_stacks + */ + std::atomic available_large_chunks_in_bytes{0}; + /** * Stack of large allocations that have been returned for reuse. */ @@ -184,10 +194,13 @@ namespace snmalloc { // Cache line align size_t size = bits::align_up(sizeof(T), 64); - size = bits::max(size, alignment); - void* p = address_space.template reserve(bits::next_pow2(size)); + size = bits::next_pow2(bits::max(size, alignment)); + void* p = address_space.template reserve(size); if (p == nullptr) return nullptr; + + peak_memory_used_bytes += size; + return new (p) T(std::forward(args)...); } @@ -195,9 +208,20 @@ namespace snmalloc void* reserve(size_t large_class) noexcept { size_t size = bits::one_at_bit(SUPERSLAB_BITS) << large_class; - + peak_memory_used_bytes += size; return address_space.template reserve(size); } + + /** + * Returns a pair of current memory usage and peak memory usage. + * Both statistics are very coarse-grained. + */ + std::pair memory_usage() + { + size_t avail = available_large_chunks_in_bytes; + size_t peak = peak_memory_used_bytes; + return {peak - avail, peak}; + } }; using Stats = AllocStats; @@ -239,6 +263,7 @@ namespace snmalloc else { stats.superslab_pop(); + memory_provider.available_large_chunks_in_bytes -= rsize; // Cross-reference alloc.h's large_dealloc decommitment condition. bool decommitted = @@ -284,18 +309,19 @@ namespace snmalloc "without low memory notifications"); } + size_t rsize = bits::one_at_bit(SUPERSLAB_BITS) << large_class; + // Cross-reference largealloc's alloc() decommitted condition. if ( (decommit_strategy != DecommitNone) && (large_class != 0 || decommit_strategy == DecommitSuper)) { - size_t rsize = bits::one_at_bit(SUPERSLAB_BITS) << large_class; - memory_provider.notify_not_using( pointer_offset(p, OS_PAGE_SIZE), rsize - OS_PAGE_SIZE); } stats.superslab_push(); + memory_provider.available_large_chunks_in_bytes += rsize; memory_provider.large_stack[large_class].push(static_cast(p)); } }; diff --git a/src/override/malloc-extensions.cc b/src/override/malloc-extensions.cc new file mode 100644 index 0000000..ff621a9 --- /dev/null +++ b/src/override/malloc-extensions.cc @@ -0,0 +1,12 @@ +#include "malloc-extensions.h" + +#include "../snmalloc.h" + +using namespace snmalloc; + +void get_malloc_info_v1(malloc_info_v1* stats) +{ + auto next_memory_usage = default_memory_provider().memory_usage(); + stats->current_memory_usage = next_memory_usage.first; + stats->peak_memory_usage = next_memory_usage.second; +} \ No newline at end of file diff --git a/src/override/malloc-extensions.h b/src/override/malloc-extensions.h new file mode 100644 index 0000000..2fc6777 --- /dev/null +++ b/src/override/malloc-extensions.h @@ -0,0 +1,34 @@ +/** + * Malloc extensions + * + * This file contains additional non-standard API surface for snmalloc. + * The API is subject to changes, but will be clearly noted in release + * notes. + */ + +/** + * Structure for returning memory used by snmalloc. + * + * The statistics are very coarse grained as they only track + * usage at the superslab/chunk level. Meta-data and object + * data is not tracked independantly. + */ +struct malloc_info_v1 +{ + /** + * Current memory usage of the allocator. Extremely coarse + * grained for efficient calculation. + */ + size_t current_memory_usage; + + /** + * High-water mark of current_memory_usage. + */ + size_t peak_memory_usage; +}; + +/** + * Populates a malloc_info_v1 structure for the latest values + * from snmalloc. + */ +void get_malloc_info_v1(malloc_info_v1* stats); diff --git a/src/test/func/memory_usage/memory_usage.cc b/src/test/func/memory_usage/memory_usage.cc new file mode 100644 index 0000000..bcea6d7 --- /dev/null +++ b/src/test/func/memory_usage/memory_usage.cc @@ -0,0 +1,103 @@ +/** + * Memory usage test + * Query memory usage repeatedly + */ + +#include +#include +#include + +#define SNMALLOC_NAME_MANGLE(a) our_##a +#include "../../../override/malloc-extensions.cc" +#include "../../../override/malloc.cc" + +using namespace snmalloc; + +bool print_memory_usage() +{ + static malloc_info_v1 last_memory_usage; + malloc_info_v1 next_memory_usage; + + get_malloc_info_v1(&next_memory_usage); + + if ( + (next_memory_usage.current_memory_usage != + last_memory_usage.current_memory_usage) || + (next_memory_usage.peak_memory_usage != + last_memory_usage.peak_memory_usage)) + { + std::cout << "Memory Usages Changed to (" + << next_memory_usage.current_memory_usage << ", " + << next_memory_usage.peak_memory_usage << ")" << std::endl; + last_memory_usage = next_memory_usage; + return true; + } + return false; +} + +std::vector allocs; + +/** + * Add allocs until the statistics have changed n times. + */ +void add_n_allocs(size_t n) +{ + while (true) + { + allocs.push_back(our_malloc(1024)); + if (print_memory_usage()) + { + n--; + if (n == 0) + break; + } + } +} + +/** + * Remove allocs until the statistics have changed n times. + */ +void remove_n_allocs(size_t n) +{ + while (true) + { + our_free(allocs.back()); + allocs.pop_back(); + if (print_memory_usage()) + { + n--; + if (n == 0) + break; + } + } +} + +int main(int argc, char** argv) +{ + UNUSED(argc); + UNUSED(argv); + + setup(); + + add_n_allocs(5); + std::cout << "Init complete!" << std::endl; + + for (int i = 0; i < 10; i++) + { + remove_n_allocs(1); + std::cout << "Phase " << i << " remove complete!" << std::endl; + add_n_allocs(2); + std::cout << "Phase " << i << " add complete!" << std::endl; + } + + for (int i = 0; i < 10; i++) + { + remove_n_allocs(2); + std::cout << "Phase " << i << " remove complete!" << std::endl; + add_n_allocs(1); + std::cout << "Phase " << i << " add complete!" << std::endl; + } + + remove_n_allocs(3); + std::cout << "Teardown complete!" << std::endl; +}