Skip to content

Commit 77ebff6

Browse files
authored
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.
1 parent 234c0e2 commit 77ebff6

File tree

4 files changed

+180
-5
lines changed

4 files changed

+180
-5
lines changed

src/mem/largealloc.h

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,17 @@ namespace snmalloc
7070
*/
7171
AddressSpaceManager<PAL> address_space = {};
7272

73+
/**
74+
* High-water mark of used memory.
75+
*/
76+
std::atomic<size_t> peak_memory_used_bytes{0};
77+
7378
public:
79+
/**
80+
* Memory current available in large_stacks
81+
*/
82+
std::atomic<size_t> available_large_chunks_in_bytes{0};
83+
7484
/**
7585
* Stack of large allocations that have been returned for reuse.
7686
*/
@@ -184,20 +194,34 @@ namespace snmalloc
184194
{
185195
// Cache line align
186196
size_t size = bits::align_up(sizeof(T), 64);
187-
size = bits::max(size, alignment);
188-
void* p = address_space.template reserve<true>(bits::next_pow2(size));
197+
size = bits::next_pow2(bits::max(size, alignment));
198+
void* p = address_space.template reserve<true>(size);
189199
if (p == nullptr)
190200
return nullptr;
201+
202+
peak_memory_used_bytes += size;
203+
191204
return new (p) T(std::forward<Args...>(args)...);
192205
}
193206

194207
template<bool committed>
195208
void* reserve(size_t large_class) noexcept
196209
{
197210
size_t size = bits::one_at_bit(SUPERSLAB_BITS) << large_class;
198-
211+
peak_memory_used_bytes += size;
199212
return address_space.template reserve<committed>(size);
200213
}
214+
215+
/**
216+
* Returns a pair of current memory usage and peak memory usage.
217+
* Both statistics are very coarse-grained.
218+
*/
219+
std::pair<size_t, size_t> memory_usage()
220+
{
221+
size_t avail = available_large_chunks_in_bytes;
222+
size_t peak = peak_memory_used_bytes;
223+
return {peak - avail, peak};
224+
}
201225
};
202226

203227
using Stats = AllocStats<NUM_SIZECLASSES, NUM_LARGE_CLASSES>;
@@ -239,6 +263,7 @@ namespace snmalloc
239263
else
240264
{
241265
stats.superslab_pop();
266+
memory_provider.available_large_chunks_in_bytes -= rsize;
242267

243268
// Cross-reference alloc.h's large_dealloc decommitment condition.
244269
bool decommitted =
@@ -284,18 +309,19 @@ namespace snmalloc
284309
"without low memory notifications");
285310
}
286311

312+
size_t rsize = bits::one_at_bit(SUPERSLAB_BITS) << large_class;
313+
287314
// Cross-reference largealloc's alloc() decommitted condition.
288315
if (
289316
(decommit_strategy != DecommitNone) &&
290317
(large_class != 0 || decommit_strategy == DecommitSuper))
291318
{
292-
size_t rsize = bits::one_at_bit(SUPERSLAB_BITS) << large_class;
293-
294319
memory_provider.notify_not_using(
295320
pointer_offset(p, OS_PAGE_SIZE), rsize - OS_PAGE_SIZE);
296321
}
297322

298323
stats.superslab_push();
324+
memory_provider.available_large_chunks_in_bytes += rsize;
299325
memory_provider.large_stack[large_class].push(static_cast<Largeslab*>(p));
300326
}
301327
};

src/override/malloc-extensions.cc

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
#include "malloc-extensions.h"
2+
3+
#include "../snmalloc.h"
4+
5+
using namespace snmalloc;
6+
7+
void get_malloc_info_v1(malloc_info_v1* stats)
8+
{
9+
auto next_memory_usage = default_memory_provider().memory_usage();
10+
stats->current_memory_usage = next_memory_usage.first;
11+
stats->peak_memory_usage = next_memory_usage.second;
12+
}

src/override/malloc-extensions.h

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/**
2+
* Malloc extensions
3+
*
4+
* This file contains additional non-standard API surface for snmalloc.
5+
* The API is subject to changes, but will be clearly noted in release
6+
* notes.
7+
*/
8+
9+
/**
10+
* Structure for returning memory used by snmalloc.
11+
*
12+
* The statistics are very coarse grained as they only track
13+
* usage at the superslab/chunk level. Meta-data and object
14+
* data is not tracked independantly.
15+
*/
16+
struct malloc_info_v1
17+
{
18+
/**
19+
* Current memory usage of the allocator. Extremely coarse
20+
* grained for efficient calculation.
21+
*/
22+
size_t current_memory_usage;
23+
24+
/**
25+
* High-water mark of current_memory_usage.
26+
*/
27+
size_t peak_memory_usage;
28+
};
29+
30+
/**
31+
* Populates a malloc_info_v1 structure for the latest values
32+
* from snmalloc.
33+
*/
34+
void get_malloc_info_v1(malloc_info_v1* stats);
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
/**
2+
* Memory usage test
3+
* Query memory usage repeatedly
4+
*/
5+
6+
#include <iostream>
7+
#include <test/setup.h>
8+
#include <vector>
9+
10+
#define SNMALLOC_NAME_MANGLE(a) our_##a
11+
#include "../../../override/malloc-extensions.cc"
12+
#include "../../../override/malloc.cc"
13+
14+
using namespace snmalloc;
15+
16+
bool print_memory_usage()
17+
{
18+
static malloc_info_v1 last_memory_usage;
19+
malloc_info_v1 next_memory_usage;
20+
21+
get_malloc_info_v1(&next_memory_usage);
22+
23+
if (
24+
(next_memory_usage.current_memory_usage !=
25+
last_memory_usage.current_memory_usage) ||
26+
(next_memory_usage.peak_memory_usage !=
27+
last_memory_usage.peak_memory_usage))
28+
{
29+
std::cout << "Memory Usages Changed to ("
30+
<< next_memory_usage.current_memory_usage << ", "
31+
<< next_memory_usage.peak_memory_usage << ")" << std::endl;
32+
last_memory_usage = next_memory_usage;
33+
return true;
34+
}
35+
return false;
36+
}
37+
38+
std::vector<void*> allocs;
39+
40+
/**
41+
* Add allocs until the statistics have changed n times.
42+
*/
43+
void add_n_allocs(size_t n)
44+
{
45+
while (true)
46+
{
47+
allocs.push_back(our_malloc(1024));
48+
if (print_memory_usage())
49+
{
50+
n--;
51+
if (n == 0)
52+
break;
53+
}
54+
}
55+
}
56+
57+
/**
58+
* Remove allocs until the statistics have changed n times.
59+
*/
60+
void remove_n_allocs(size_t n)
61+
{
62+
while (true)
63+
{
64+
our_free(allocs.back());
65+
allocs.pop_back();
66+
if (print_memory_usage())
67+
{
68+
n--;
69+
if (n == 0)
70+
break;
71+
}
72+
}
73+
}
74+
75+
int main(int argc, char** argv)
76+
{
77+
UNUSED(argc);
78+
UNUSED(argv);
79+
80+
setup();
81+
82+
add_n_allocs(5);
83+
std::cout << "Init complete!" << std::endl;
84+
85+
for (int i = 0; i < 10; i++)
86+
{
87+
remove_n_allocs(1);
88+
std::cout << "Phase " << i << " remove complete!" << std::endl;
89+
add_n_allocs(2);
90+
std::cout << "Phase " << i << " add complete!" << std::endl;
91+
}
92+
93+
for (int i = 0; i < 10; i++)
94+
{
95+
remove_n_allocs(2);
96+
std::cout << "Phase " << i << " remove complete!" << std::endl;
97+
add_n_allocs(1);
98+
std::cout << "Phase " << i << " add complete!" << std::endl;
99+
}
100+
101+
remove_n_allocs(3);
102+
std::cout << "Teardown complete!" << std::endl;
103+
}

0 commit comments

Comments
 (0)