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:
committed by
GitHub
parent
234c0e2e8e
commit
77ebff6909
@@ -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));
|
||||
}
|
||||
};
|
||||
|
||||
12
src/override/malloc-extensions.cc
Normal file
12
src/override/malloc-extensions.cc
Normal 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;
|
||||
}
|
||||
34
src/override/malloc-extensions.h
Normal file
34
src/override/malloc-extensions.h
Normal 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);
|
||||
103
src/test/func/memory_usage/memory_usage.cc
Normal file
103
src/test/func/memory_usage/memory_usage.cc
Normal 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;
|
||||
}
|
||||
Reference in New Issue
Block a user