Skip to content

Commit

Permalink
Instrument GC with memory profiler implementation
Browse files Browse the repository at this point in the history
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)
  • Loading branch information
maleadt committed Oct 18, 2019
1 parent 6650c90 commit 13f6341
Show file tree
Hide file tree
Showing 12 changed files with 391 additions and 51 deletions.
24 changes: 22 additions & 2 deletions base/error.jl
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,16 @@ struct InterpreterIP
mod::Union{Module,Nothing}
end

# formatted backtrace buffers can contain all types of objects (none for now though)
const BackTraceEntry = Union{Ptr{Nothing}, InterpreterIP}
struct AllocationInfo
T::Union{Nothing,Type}
address::Ptr{Cvoid}
time::Float64
allocsz::Csize_t
tag::UInt16
end

# formatted backtrace buffers can contain all types of objects
const BackTraceEntry = Union{Ptr{Nothing}, InterpreterIP, AllocationInfo}
# but only some correspond with actual instruction pointers
const InstructionPointer = Union{Ptr{Nothing}, InterpreterIP}

Expand Down Expand Up @@ -114,6 +122,18 @@ function _reformat_bt(bt, Wanted::Type=BackTraceEntry)
end
push!(ret, InterpreterIP(code, header, mod))
end
elseif tag == 2 # JL_BT_ALLOCINFO_FRAME_TAG
if AllocationInfo <: Wanted
#@assert header == 0
#@assert njlvalues == 1
type = unsafe_pointer_to_objref(convert(Ptr{Any}, bt[i+2]))
#@assert nuintvals == 4
address = reinterpret(Ptr{Cvoid}, bt[i+3])
time = reinterpret(Cdouble, bt[i+4])
allocsz = reinterpret(UInt, bt[i+5])
tag = reinterpret(UInt, bt[i+6])
push!(ret, AllocationInfo(type, address, time, allocsz, tag))
end
else
# Tags we don't know about are an error
throw(ArgumentError("Unexpected extended backtrace entry tag $tag at bt[$i]"))
Expand Down
12 changes: 10 additions & 2 deletions src/array.c
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ static jl_array_t *_new_array_(jl_value_t *atype, uint32_t ndims, size_t *dims,
tsz += tot;
tsz = JL_ARRAY_ALIGN(tsz, JL_SMALL_BYTE_ALIGNMENT); // align whole object
a = (jl_array_t*)jl_gc_alloc(ptls, tsz, atype);
jl_memprofile_set_typeof(a, atype);
// No allocation or safepoint allowed after this
a->flags.how = 0;
data = (char*)a + doffs;
Expand All @@ -107,12 +108,14 @@ static jl_array_t *_new_array_(jl_value_t *atype, uint32_t ndims, size_t *dims,
else {
tsz = JL_ARRAY_ALIGN(tsz, JL_CACHE_BYTE_ALIGNMENT); // align whole object
data = jl_gc_managed_malloc(tot);
jl_memprofile_set_typeof(data, atype);
// Allocate the Array **after** allocating the data
// to make sure the array is still young
a = (jl_array_t*)jl_gc_alloc(ptls, tsz, atype);
// No allocation or safepoint allowed after this
a->flags.how = 2;
jl_gc_track_malloced_array(ptls, a);
jl_memprofile_set_typeof(a, atype);
if (!isunboxed || isunion)
// need to zero out isbits union array selector bytes to ensure a valid type index
memset(data, 0, tot);
Expand Down Expand Up @@ -334,7 +337,9 @@ JL_DLLEXPORT jl_array_t *jl_ptr_to_array_1d(jl_value_t *atype, void *data,
if (own_buffer) {
a->flags.how = 2;
jl_gc_track_malloced_array(ptls, a);
jl_gc_count_allocd(nel*elsz + (elsz == 1 ? 1 : 0));
jl_gc_count_allocd(a, nel*elsz + (elsz == 1 ? 1 : 0),
JL_MEMPROF_TAG_DOMAIN_CPU | JL_MEMPROF_TAG_ALLOC_STDALLOC);
jl_memprofile_set_typeof(a, atype);
}
else {
a->flags.how = 0;
Expand Down Expand Up @@ -401,7 +406,9 @@ JL_DLLEXPORT jl_array_t *jl_ptr_to_array(jl_value_t *atype, void *data,
if (own_buffer) {
a->flags.how = 2;
jl_gc_track_malloced_array(ptls, a);
jl_gc_count_allocd(nel*elsz + (elsz == 1 ? 1 : 0));
jl_gc_count_allocd(a, nel*elsz + (elsz == 1 ? 1 : 0),
JL_MEMPROF_TAG_DOMAIN_CPU | JL_MEMPROF_TAG_ALLOC_STDALLOC);
jl_memprofile_set_typeof(a, atype);
}
else {
a->flags.how = 0;
Expand Down Expand Up @@ -669,6 +676,7 @@ static int NOINLINE array_resize_buffer(jl_array_t *a, size_t newlen)
a->flags.how = 1;
jl_gc_wb_buf(a, a->data, nbytes);
}
jl_memprofile_set_typeof(a->data, jl_typeof(a));
}
if (JL_ARRAY_IMPL_NUL && elsz == 1 && !isbitsunion)
memset((char*)a->data + oldnbytes - 1, 0, nbytes - oldnbytes + 1);
Expand Down
Loading

0 comments on commit 13f6341

Please sign in to comment.