Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

use a non-stack-based algorithm to compute summarysize #20161

Merged
merged 1 commit into from
Jan 23, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
153 changes: 153 additions & 0 deletions base/summarysize.jl
Original file line number Diff line number Diff line change
@@ -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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the old computation for UnionAll also added summarysize(obj.body, seen, excl) + summarysize(obj.var, seen, excl), is this new version undercounting?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the fields are handle elsewhere now. (unrelated, but the old version was also over-counting the Core.sizeof)

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
13 changes: 11 additions & 2 deletions test/misc.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down