Added simple stats tracking memory usage (#241)

These statistics can be maintained with effectively zero cost to
realistic applications.  They do not track the precise amount of
memory used, but are an over-approximation.
This commit is contained in:
Matthew Parkinson
2020-08-28 14:01:52 +01:00
committed by GitHub
parent 234c0e2e8e
commit 77ebff6909
4 changed files with 180 additions and 5 deletions

View File

@@ -70,7 +70,17 @@ namespace snmalloc
*/
AddressSpaceManager<PAL> address_space = {};
/**
* High-water mark of used memory.
*/
std::atomic<size_t> peak_memory_used_bytes{0};
public:
/**
* Memory current available in large_stacks
*/
std::atomic<size_t> 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<true>(bits::next_pow2(size));
size = bits::next_pow2(bits::max(size, alignment));
void* p = address_space.template reserve<true>(size);
if (p == nullptr)
return nullptr;
peak_memory_used_bytes += size;
return new (p) T(std::forward<Args...>(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<committed>(size);
}
/**
* Returns a pair of current memory usage and peak memory usage.
* Both statistics are very coarse-grained.
*/
std::pair<size_t, size_t> memory_usage()
{
size_t avail = available_large_chunks_in_bytes;
size_t peak = peak_memory_used_bytes;
return {peak - avail, peak};
}
};
using Stats = AllocStats<NUM_SIZECLASSES, NUM_LARGE_CLASSES>;
@@ -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<Largeslab*>(p));
}
};

View File

@@ -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;
}

View File

@@ -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);

View File

@@ -0,0 +1,103 @@
/**
* Memory usage test
* Query memory usage repeatedly
*/
#include <iostream>
#include <test/setup.h>
#include <vector>
#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<void*> 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;
}