Skip to content

Commit

Permalink
use a non-stack-based algorithm to compute summarysize
Browse files Browse the repository at this point in the history
in addition to not using stack space, this doesn't need as many special cases
  • Loading branch information
vtjnash committed Jan 20, 2017
1 parent 95cb94c commit a62e179
Show file tree
Hide file tree
Showing 4 changed files with 151 additions and 150 deletions.
148 changes: 0 additions & 148 deletions base/interactiveutil.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
148 changes: 148 additions & 0 deletions base/summarysize.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
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 argument `exclude` specifies the types of objects to exclude from the traversal.
Keyword argument `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}, chargeall::ANY = Union{TypeMapEntry, Method, 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 nfields(x) > 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
1 change: 1 addition & 0 deletions base/sysimg.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
4 changes: 2 additions & 2 deletions test/misc.jl
Original file line number Diff line number Diff line change
Expand Up @@ -293,8 +293,8 @@ 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(Int)
module _test_whos_
export x
x = 1.0
Expand Down

0 comments on commit a62e179

Please sign in to comment.