Skip to content

Commit efbe3fa

Browse files
committed
Instrument GC with memory profiler implementation
This adds C support for a memory profiler within the GC, tracking locations of allocations, deallocations, etc... It operates in a similar manner as the time profiler with single large buffers setup beforehand through an initialization function, reducing the need for expensive allocations while the program being measured is running. The memory profiler instruments the GC in all locations that the GC statistics themselves are being modified (e.g. `gc_num.allocd` and `gc_num.freed`) by introducing new helper functions `jl_gc_count_{allocd,freed,reallocd}()`. Those utility functions call the `jl_memprofile_track_{de,}alloc()` method to register an address, a size and a tag with the memory profiler. We also track type information as this can be critically helpful when debugging, and to do so without breaking API guarantees we insert methods to set the type of a chunk of memory after allocating it where necessary. The tagging system allows the memory profiler to disambiguate, at profile time, between e.g. pooled allocations and the "big" allocator. It also allows the memory allocator to support tracking multiple "memory domains", e.g. a GPU support package could manually call `jl_memprofile_track_alloc()` any time a chunk of memory is allocated on the GPU so as to use the same system. By default, all values are tracked, however one can set a `memprof_tag_filter` value to track only the values you are most interested in. (E.g. only CPU domain big allocations)
1 parent 026c788 commit efbe3fa

12 files changed

+392
-51
lines changed

base/error.jl

+22-2
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,16 @@ struct InterpreterIP
6565
mod::Union{Module,Nothing}
6666
end
6767

68-
# formatted backtrace buffers can contain all types of objects (none for now though)
69-
const BackTraceEntry = Union{Ptr{Nothing}, InterpreterIP}
68+
struct AllocationInfo
69+
T::Union{Nothing,Type}
70+
address::Ptr{Cvoid}
71+
time::Float64
72+
allocsz::Csize_t
73+
tag::UInt16
74+
end
75+
76+
# formatted backtrace buffers can contain all types of objects
77+
const BackTraceEntry = Union{Ptr{Nothing}, InterpreterIP, AllocationInfo}
7078
# but only some correspond with actual instruction pointers
7179
const InstructionPointer = Union{Ptr{Nothing}, InterpreterIP}
7280

@@ -114,6 +122,18 @@ function _reformat_bt(bt, Wanted::Type=BackTraceEntry)
114122
end
115123
push!(ret, InterpreterIP(code, header, mod))
116124
end
125+
elseif tag == 2 # JL_BT_ALLOCINFO_FRAME_TAG
126+
if AllocationInfo <: Wanted
127+
#@assert header == 0
128+
#@assert njlvalues == 1
129+
type = unsafe_pointer_to_objref(convert(Ptr{Any}, bt[i+2]))
130+
#@assert nuintvals == 4
131+
address = reinterpret(Ptr{Cvoid}, bt[i+3])
132+
time = reinterpret(Cdouble, bt[i+4])
133+
allocsz = reinterpret(UInt, bt[i+5])
134+
tag = reinterpret(UInt, bt[i+6])
135+
push!(ret, AllocationInfo(type, address, time, allocsz, tag))
136+
end
117137
else
118138
# Tags we don't know about are an error
119139
throw(ArgumentError("Unexpected extended backtrace entry tag $tag at bt[$i]"))

src/array.c

+10-2
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ static jl_array_t *_new_array_(jl_value_t *atype, uint32_t ndims, size_t *dims,
9898
tsz += tot;
9999
tsz = JL_ARRAY_ALIGN(tsz, JL_SMALL_BYTE_ALIGNMENT); // align whole object
100100
a = (jl_array_t*)jl_gc_alloc(ptls, tsz, atype);
101+
jl_memprofile_set_typeof(a, atype);
101102
// No allocation or safepoint allowed after this
102103
a->flags.how = 0;
103104
data = (char*)a + doffs;
@@ -107,12 +108,14 @@ static jl_array_t *_new_array_(jl_value_t *atype, uint32_t ndims, size_t *dims,
107108
else {
108109
tsz = JL_ARRAY_ALIGN(tsz, JL_CACHE_BYTE_ALIGNMENT); // align whole object
109110
data = jl_gc_managed_malloc(tot);
111+
jl_memprofile_set_typeof(data, atype);
110112
// Allocate the Array **after** allocating the data
111113
// to make sure the array is still young
112114
a = (jl_array_t*)jl_gc_alloc(ptls, tsz, atype);
113115
// No allocation or safepoint allowed after this
114116
a->flags.how = 2;
115117
jl_gc_track_malloced_array(ptls, a);
118+
jl_memprofile_set_typeof(a, atype);
116119
if (!isunboxed || isunion)
117120
// need to zero out isbits union array selector bytes to ensure a valid type index
118121
memset(data, 0, tot);
@@ -334,7 +337,9 @@ JL_DLLEXPORT jl_array_t *jl_ptr_to_array_1d(jl_value_t *atype, void *data,
334337
if (own_buffer) {
335338
a->flags.how = 2;
336339
jl_gc_track_malloced_array(ptls, a);
337-
jl_gc_count_allocd(nel*elsz + (elsz == 1 ? 1 : 0));
340+
jl_gc_count_allocd(a, nel*elsz + (elsz == 1 ? 1 : 0),
341+
JL_MEMPROF_TAG_DOMAIN_CPU | JL_MEMPROF_TAG_ALLOC_STDALLOC);
342+
jl_memprofile_set_typeof(a, atype);
338343
}
339344
else {
340345
a->flags.how = 0;
@@ -401,7 +406,9 @@ JL_DLLEXPORT jl_array_t *jl_ptr_to_array(jl_value_t *atype, void *data,
401406
if (own_buffer) {
402407
a->flags.how = 2;
403408
jl_gc_track_malloced_array(ptls, a);
404-
jl_gc_count_allocd(nel*elsz + (elsz == 1 ? 1 : 0));
409+
jl_gc_count_allocd(a, nel*elsz + (elsz == 1 ? 1 : 0),
410+
JL_MEMPROF_TAG_DOMAIN_CPU | JL_MEMPROF_TAG_ALLOC_STDALLOC);
411+
jl_memprofile_set_typeof(a, atype);
405412
}
406413
else {
407414
a->flags.how = 0;
@@ -669,6 +676,7 @@ static int NOINLINE array_resize_buffer(jl_array_t *a, size_t newlen)
669676
a->flags.how = 1;
670677
jl_gc_wb_buf(a, a->data, nbytes);
671678
}
679+
jl_memprofile_set_typeof(a->data, jl_typeof(a));
672680
}
673681
if (JL_ARRAY_IMPL_NUL && elsz == 1 && !isbitsunion)
674682
memset((char*)a->data + oldnbytes - 1, 0, nbytes - oldnbytes + 1);

0 commit comments

Comments
 (0)