diff --git a/base/interactiveutil.jl b/base/interactiveutil.jl index 8ce74ff203dd6..876d123d14483 100644 --- a/base/interactiveutil.jl +++ b/base/interactiveutil.jl @@ -716,151 +716,3 @@ function whos(io::IO=STDOUT, m::Module=current_module(), pattern::Regex=r"") end whos(m::Module, pat::Regex=r"") = whos(STDOUT, m, pat) whos(pat::Regex) = whos(STDOUT, current_module(), pat) - -################################################################################# - -""" - Base.summarysize(obj; exclude=Union{Module,DataType,TypeName}) -> Int - -Compute the amount of memory used by all unique objects reachable from the argument. -Keyword argument `exclude` specifies a type of objects to exclude from the traversal. -""" -summarysize(obj; exclude = Union{Module,DataType,TypeName}) = - summarysize(obj, ObjectIdDict(), exclude) - -summarysize(obj::Symbol, seen, excl) = 0 - -function summarysize(obj::UnionAll, seen, excl) - return 2*sizeof(Int) + summarysize(obj.body, seen, excl) + summarysize(obj.var, seen, excl) -end - -function summarysize(obj::DataType, seen, excl) - key = pointer_from_objref(obj) - haskey(seen, key) ? (return 0) : (seen[key] = true) - size = 7*sizeof(Int) + 6*sizeof(Int32) + 4*nfields(obj) + ifelse(Sys.WORD_SIZE == 64, 4, 0) - size += summarysize(obj.parameters, seen, excl)::Int - size += summarysize(obj.types, seen, excl)::Int - return size -end - -function summarysize(obj::TypeName, seen, excl) - key = pointer_from_objref(obj) - haskey(seen, key) ? (return 0) : (seen[key] = true) - return Core.sizeof(obj) + (isdefined(obj,:mt) ? summarysize(obj.mt, seen, excl) : 0) -end - -summarysize(obj::ANY, seen, excl) = _summarysize(obj, seen, excl) -# define the general case separately to make sure it is not specialized for every type -function _summarysize(obj::ANY, seen, excl) - key = pointer_from_objref(obj) - haskey(seen, key) ? (return 0) : (seen[key] = true) - size = Core.sizeof(obj) - ft = typeof(obj).types - for i in 1:nfields(obj) - if !isbits(ft[i]) && isdefined(obj,i) - val = getfield(obj, i) - if !isa(val,excl) - size += summarysize(val, seen, excl)::Int - end - end - end - return size -end - -function summarysize(obj::Array, seen, excl) - haskey(seen, obj) ? (return 0) : (seen[obj] = true) - size = Core.sizeof(obj) - # TODO: add size of jl_array_t - if !isbits(eltype(obj)) - for i in 1:length(obj) - if ccall(:jl_array_isassigned, Cint, (Any, UInt), obj, i-1) == 1 - val = obj[i] - if !isa(val, excl) - size += summarysize(val, seen, excl)::Int - end - end - end - end - return size -end - -summarysize(s::String, seen, excl) = sizeof(Int) + sizeof(s) - -function summarysize(obj::SimpleVector, seen, excl) - key = pointer_from_objref(obj) - haskey(seen, key) ? (return 0) : (seen[key] = true) - size = Core.sizeof(obj) - for i in 1:length(obj) - if isassigned(obj, i) - val = obj[i] - if !isa(val, excl) - size += summarysize(val, seen, excl)::Int - end - end - end - return size -end - -function summarysize(obj::Module, seen, excl) - haskey(seen, obj) ? (return 0) : (seen[obj] = true) - size::Int = Core.sizeof(obj) - for binding in names(obj, true) - if isdefined(obj, binding) && !isdeprecated(obj, binding) - value = getfield(obj, binding) - if !isa(value, Module) || module_parent(value) === obj - size += summarysize(value, seen, excl)::Int - vt = isa(value,DataType) ? value : typeof(value) - if vt.name.module === obj - if vt !== value - size += summarysize(vt, seen, excl)::Int - end - # charge a TypeName to its module - size += summarysize(vt.name, seen, excl)::Int - end - end - end - end - return size -end - -function summarysize(obj::Task, seen, excl) - haskey(seen, obj) ? (return 0) : (seen[obj] = true) - size::Int = Core.sizeof(obj) - if isdefined(obj, :code) - size += summarysize(obj.code, seen, excl)::Int - end - size += summarysize(obj.storage, seen, excl)::Int - size += summarysize(obj.backtrace, seen, excl)::Int - size += summarysize(obj.donenotify, seen, excl)::Int - size += summarysize(obj.exception, seen, excl)::Int - size += summarysize(obj.result, seen, excl)::Int - # TODO: add stack size, and possibly traverse stack roots - return size -end - -function summarysize(obj::MethodTable, seen, excl) - haskey(seen, obj) ? (return 0) : (seen[obj] = true) - size::Int = Core.sizeof(obj) - size += summarysize(obj.defs, seen, excl)::Int - size += summarysize(obj.cache, seen, excl)::Int - if isdefined(obj, :kwsorter) - size += summarysize(obj.kwsorter, seen, excl)::Int - end - return size -end - -function summarysize(m::TypeMapEntry, seen, excl) - size::Int = 0 - while true # specialized to prevent stack overflow while following this linked list - haskey(seen, m) ? (return size) : (seen[m] = true) - size += Core.sizeof(m) - if isdefined(m, :func) - size += summarysize(m.func, seen, excl)::Int - end - size += summarysize(m.sig, seen, excl)::Int - size += summarysize(m.tvars, seen, excl)::Int - m.next === nothing && break - m = m.next::TypeMapEntry - end - return size -end diff --git a/base/summarysize.jl b/base/summarysize.jl new file mode 100644 index 0000000000000..25b7679795e48 --- /dev/null +++ b/base/summarysize.jl @@ -0,0 +1,153 @@ +immutable SummarySize + seen::ObjectIdDict + frontier_x::Vector{Any} + frontier_i::Vector{Int} + exclude::Any + chargeall::Any +end + + +""" + Base.summarysize(obj; exclude=Union{...}, chargeall=Union{...}) -> Int + +Compute the amount of memory used by all unique objects reachable from the argument. + +# Keyword Arguments +* `exclude`: specifies the types of objects to exclude from the traversal. +* `chargeall`: specifies the types of objects to always charge the size of all of their fields, + even if those fields would normally be excluded. +""" +function summarysize(obj::ANY; + exclude::ANY = Union{DataType, TypeName, Method}, + chargeall::ANY = Union{TypeMapEntry, Core.MethodInstance}) + ss = SummarySize(ObjectIdDict(), Any[], Int[], exclude, chargeall) + size::Int = ss(obj) + while !isempty(ss.frontier_x) + # DFS heap traversal of everything without a specialization + # BFS heap traversal of anything with a specialization + x = ss.frontier_x[end] + i = ss.frontier_i[end] + val = nothing + if isa(x, SimpleVector) + nf = length(x) + if isassigned(x, i) + val = x[i] + end + elseif isa(x, Array) + nf = length(x) + if ccall(:jl_array_isassigned, Cint, (Any, UInt), x, i - 1) != 0 + val = x[i] + end + else + nf = nfields(x) + ft = typeof(x).types + if !isbits(ft[i]) && isdefined(x, i) + val = getfield(x, i) + end + end + if nf > i + ss.frontier_i[end] = i + 1 + else + pop!(ss.frontier_x) + pop!(ss.frontier_i) + end + if val !== nothing && !isa(val, Module) && (!isa(val, ss.exclude) || isa(x, ss.chargeall)) + size += ss(val)::Int + end + end + return size +end + +(ss::SummarySize)(obj::ANY) = _summarysize(ss, obj) +# define the general case separately to make sure it is not specialized for every type +@noinline function _summarysize(ss::SummarySize, obj::ANY) + key = pointer_from_objref(obj) + haskey(ss.seen, key) ? (return 0) : (ss.seen[key] = true) + if nfields(obj) > 0 + push!(ss.frontier_x, obj) + push!(ss.frontier_i, 1) + end + if isa(obj, UnionAll) + # black-list of items that don't have a Core.sizeof + return 2 * sizeof(Int) + end + return Core.sizeof(obj) +end + +(::SummarySize)(obj::Symbol) = 0 +(::SummarySize)(obj::SummarySize) = 0 +(::SummarySize)(obj::String) = Core.sizeof(Int) + Core.sizeof(obj) + +function (ss::SummarySize)(obj::DataType) + key = pointer_from_objref(obj) + haskey(ss.seen, key) ? (return 0) : (ss.seen[key] = true) + size::Int = 7 * Core.sizeof(Int) + 6 * Core.sizeof(Int32) + size += 4 * nfields(obj) + ifelse(Sys.WORD_SIZE == 64, 4, 0) + size += ss(obj.parameters)::Int + size += ss(obj.types)::Int + return size +end + +function (ss::SummarySize)(obj::TypeName) + key = pointer_from_objref(obj) + haskey(ss.seen, key) ? (return 0) : (ss.seen[key] = true) + return Core.sizeof(obj) + (isdefined(obj, :mt) ? ss(obj.mt) : 0) +end + +function (ss::SummarySize)(obj::Array) + haskey(ss.seen, obj) ? (return 0) : (ss.seen[obj] = true) + size::Int = Core.sizeof(obj) + # TODO: add size of jl_array_t + if !isbits(eltype(obj)) && !isempty(obj) + push!(ss.frontier_x, obj) + push!(ss.frontier_i, 1) + end + return size +end + +function (ss::SummarySize)(obj::SimpleVector) + key = pointer_from_objref(obj) + haskey(ss.seen, key) ? (return 0) : (ss.seen[key] = true) + size::Int = Core.sizeof(obj) + if !isempty(obj) + push!(ss.frontier_x, obj) + push!(ss.frontier_i, 1) + end + return size +end + +function (ss::SummarySize)(obj::Module) + haskey(ss.seen, obj) ? (return 0) : (ss.seen[obj] = true) + size::Int = Core.sizeof(obj) + for binding in names(obj, true) + if isdefined(obj, binding) && !isdeprecated(obj, binding) + value = getfield(obj, binding) + if !isa(value, Module) || module_parent(value) === obj + size += ss(value)::Int + if isa(value, UnionAll) + value = unwrap_unionall(value) + end + if isa(value, DataType) && value.name.module === obj && value.name.name === binding + # charge a TypeName to its module (but not to the type) + size += ss(value.name)::Int + end + end + end + end + return size +end + +function (ss::SummarySize)(obj::Task) + haskey(ss.seen, obj) ? (return 0) : (ss.seen[obj] = true) + size::Int = Core.sizeof(obj) + if isdefined(obj, :code) + size += ss(obj.code)::Int + end + size += ss(obj.storage)::Int + size += ss(obj.backtrace)::Int + size += ss(obj.donenotify)::Int + size += ss(obj.exception)::Int + size += ss(obj.result)::Int + # TODO: add stack size, and possibly traverse stack roots + return size +end diff --git a/base/sysimg.jl b/base/sysimg.jl index d4035fcc410d3..8fa9991f8daa5 100644 --- a/base/sysimg.jl +++ b/base/sysimg.jl @@ -300,6 +300,7 @@ include("datafmt.jl") importall .DataFmt include("deepcopy.jl") include("interactiveutil.jl") +include("summarysize.jl") include("replutil.jl") include("test.jl") include("i18n.jl") diff --git a/test/misc.jl b/test/misc.jl index af9972d1f8210..c5fa181ace766 100644 --- a/test/misc.jl +++ b/test/misc.jl @@ -293,8 +293,17 @@ v11801, t11801 = @timed sin(1) # interactive utilities import Base.summarysize -@test summarysize(Core) > summarysize(Core.Inference) > Core.sizeof(Core) -@test summarysize(Base) > 10_000*sizeof(Int) +@test summarysize(Core) > (summarysize(Core.Inference) + Base.summarysize(Core.Intrinsics)) > Core.sizeof(Core) +@test summarysize(Base) > 100_000 * sizeof(Ptr) + +let R = Ref{Any}(nothing), depth = 10^6 + for i = 1:depth + R = Ref{Any}(R) + end + R = Core.svec(R, R) + @test summarysize(R) == (depth + 4) * sizeof(Ptr) +end + module _test_whos_ export x x = 1.0