Skip to content

Commit 345a791

Browse files
committed
Base: Add count argument to Base.summarysize
Frequently the number of allocations can be more important for performance than the amount of memory allocated. This adds a small utility to `summarysize` to allow counting the number of unique reachable objects, which can be useful to understand, e.g., if you are performing many small allocations or how many of the allocations in a function ended up reachable from the result.
1 parent 056e68b commit 345a791

File tree

1 file changed

+35
-24
lines changed

1 file changed

+35
-24
lines changed

base/summarysize.jl

Lines changed: 35 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,20 @@ struct SummarySize
66
frontier_i::Vector{Int}
77
exclude::Any
88
chargeall::Any
9+
count::Bool
910
end
1011

1112
nth_pointer_isdefined(obj, i::Int) = ccall(:jl_nth_pointer_isdefined, Cint, (Any, Csize_t), obj, i-1) != 0
1213
get_nth_pointer(obj, i::Int) = ccall(:jl_get_nth_pointer, Any, (Any, Csize_t), obj, i-1)
1314

1415
"""
15-
Base.summarysize(obj; exclude=Union{...}, chargeall=Union{...})::Int
16+
Base.summarysize(obj; count = false, exclude=Union{...}, chargeall=Union{...})::Int
1617
17-
Compute the amount of memory, in bytes, used by all unique objects reachable from the argument.
18+
Compute all unique objects reachable from the argument and return either their size in
19+
memory (in bytes) or their quantity.
1820
1921
# Keyword Arguments
22+
- `count`: if true, returns object quantity rather than total object size.
2023
- `exclude`: specifies the types of objects to exclude from the traversal.
2124
- `chargeall`: specifies the types of objects to always charge the size of all of their
2225
fields, even if those fields would normally be excluded.
@@ -33,29 +36,33 @@ julia> Base.summarysize(Ref(rand(100)))
3336
3437
julia> sizeof(Ref(rand(100)))
3538
8
39+
40+
julia> Base.summarysize(Core.svec(1.0, "testing", true); count=true)
41+
4
3642
```
3743
"""
3844
function summarysize(obj;
45+
count::Bool = false,
3946
exclude = Union{DataType, Core.TypeName, Core.MethodInstance},
4047
chargeall = Union{Core.TypeMapEntry, Method})
4148
@nospecialize obj exclude chargeall
42-
ss = SummarySize(IdDict(), Any[], Int[], exclude, chargeall)
49+
ss = SummarySize(IdDict(), Any[], Int[], exclude, chargeall, count)
4350
size::Int = ss(obj)
4451
while !isempty(ss.frontier_x)
4552
# DFS heap traversal of everything without a specialization
4653
# BFS heap traversal of anything with a specialization
4754
x = ss.frontier_x[end]
4855
i = ss.frontier_i[end]
4956
val = nothing
50-
if isa(x, SimpleVector)
57+
if isa(x, Core.SimpleVector)
5158
nf = length(x)
5259
if isassigned(x, i)
5360
val = x[i]
5461
end
5562
elseif isa(x, GenericMemory)
5663
T = eltype(x)
5764
if Base.allocatedinline(T)
58-
np = datatype_npointers(T)
65+
np = Base.datatype_npointers(T)
5966
nf = length(x) * np
6067
idx = (i-1) ÷ np + 1
6168
if @inbounds @inline isassigned(x, idx)
@@ -72,7 +79,7 @@ function summarysize(obj;
7279
end
7380
end
7481
else
75-
nf = datatype_npointers(typeof(x))
82+
nf = Base.datatype_npointers(typeof(x))
7683
if nth_pointer_isdefined(x, i)
7784
val = get_nth_pointer(x, i)
7885
end
@@ -90,15 +97,15 @@ function summarysize(obj;
9097
return size
9198
end
9299

93-
(ss::SummarySize)(@nospecialize obj) = _summarysize(ss, obj)
100+
(ss::SummarySize)(@nospecialize obj) = _summarysize(ss, obj, ss.count)
94101
# define the general case separately to make sure it is not specialized for every type
95-
@noinline function _summarysize(ss::SummarySize, @nospecialize obj)
96-
issingletontype(typeof(obj)) && return 0
102+
@noinline function _summarysize(ss::SummarySize, @nospecialize(obj), count::Bool)
103+
Base.issingletontype(typeof(obj)) && return 0
97104
# NOTE: this attempts to discover multiple copies of the same immutable value,
98105
# and so is somewhat approximate.
99106
key = ccall(:jl_value_ptr, Ptr{Cvoid}, (Any,), obj)
100107
haskey(ss.seen, key) ? (return 0) : (ss.seen[key] = true)
101-
if datatype_npointers(typeof(obj)) > 0
108+
if Base.datatype_npointers(typeof(obj)) > 0
102109
push!(ss.frontier_x, obj)
103110
push!(ss.frontier_i, 1)
104111
end
@@ -112,7 +119,7 @@ end
112119
# 0-field mutable structs are not unique
113120
return gc_alignment(0)
114121
end
115-
return sz
122+
return count ? 1 : sz
116123
end
117124

118125
(::SummarySize)(obj::Symbol) = 0
@@ -121,14 +128,14 @@ end
121128
function (ss::SummarySize)(obj::String)
122129
key = ccall(:jl_value_ptr, Ptr{Cvoid}, (Any,), obj)
123130
haskey(ss.seen, key) ? (return 0) : (ss.seen[key] = true)
124-
return Core.sizeof(Int) + Core.sizeof(obj)
131+
return (ss.count ? 1 : (Core.sizeof(Int) + Core.sizeof(obj)))
125132
end
126133

127134
function (ss::SummarySize)(obj::DataType)
128135
key = pointer_from_objref(obj)
129136
haskey(ss.seen, key) ? (return 0) : (ss.seen[key] = true)
130-
size::Int = 7 * Core.sizeof(Int) + 6 * Core.sizeof(Int32)
131-
size += 4 * nfields(obj) + ifelse(Sys.WORD_SIZE == 64, 4, 0)
137+
size::Int = (ss.count ? 1 : (7 * Core.sizeof(Int) + 6 * Core.sizeof(Int32)))
138+
size += (ss.count ? 1 : (4 * nfields(obj) + ifelse(Sys.WORD_SIZE == 64, 4, 0)))
132139
size += ss(obj.parameters)::Int
133140
if isdefined(obj, :types)
134141
size += ss(obj.types)::Int
@@ -139,17 +146,21 @@ end
139146
function (ss::SummarySize)(obj::Core.TypeName)
140147
key = pointer_from_objref(obj)
141148
haskey(ss.seen, key) ? (return 0) : (ss.seen[key] = true)
142-
return Core.sizeof(obj)
149+
return (ss.count ? 1 : Core.sizeof(obj))
143150
end
144151

145152
function (ss::SummarySize)(obj::GenericMemory)
146153
haskey(ss.seen, obj) ? (return 0) : (ss.seen[obj] = true)
147-
headersize = 2*sizeof(Int)
148-
size::Int = headersize
149-
datakey = unsafe_convert(Ptr{Cvoid}, obj)
154+
headersize = 2 * sizeof(Int)
155+
size::Int = (ss.count ? 1 : headersize)
156+
datakey = Base.unsafe_convert(Ptr{Cvoid}, obj)
150157
if !haskey(ss.seen, datakey)
151158
ss.seen[datakey] = true
152-
size += sizeof(obj)
159+
if !ss.count
160+
size += sizeof(obj)
161+
elseif pointer_from_objref(obj) + headersize != datakey
162+
size += 1
163+
end
153164
T = eltype(obj)
154165
if !isempty(obj) && T !== Symbol && (!Base.allocatedinline(T) || (T isa DataType && !Base.datatype_pointerfree(T)))
155166
push!(ss.frontier_x, obj)
@@ -159,10 +170,10 @@ function (ss::SummarySize)(obj::GenericMemory)
159170
return size
160171
end
161172

162-
function (ss::SummarySize)(obj::SimpleVector)
173+
function (ss::SummarySize)(obj::Core.SimpleVector)
163174
key = pointer_from_objref(obj)
164175
haskey(ss.seen, key) ? (return 0) : (ss.seen[key] = true)
165-
size::Int = Core.sizeof(obj)
176+
size::Int = (ss.count ? 1 : Core.sizeof(obj))
166177
if !isempty(obj)
167178
push!(ss.frontier_x, obj)
168179
push!(ss.frontier_i, 1)
@@ -172,7 +183,7 @@ end
172183

173184
function (ss::SummarySize)(obj::Module)
174185
haskey(ss.seen, obj) ? (return 0) : (ss.seen[obj] = true)
175-
size::Int = Core.sizeof(obj)
186+
size::Int = (ss.count ? 1 : Core.sizeof(obj))
176187
for binding in names(obj, all = true)
177188
if isdefined(obj, binding) && !isdeprecated(obj, binding)
178189
value = getfield(obj, binding)
@@ -193,7 +204,7 @@ end
193204

194205
function (ss::SummarySize)(obj::Task)
195206
haskey(ss.seen, obj) ? (return 0) : (ss.seen[obj] = true)
196-
size::Int = Core.sizeof(obj)
207+
size::Int = (ss.count ? 1 : Core.sizeof(obj))
197208
if isdefined(obj, :code)
198209
size += ss(obj.code)::Int
199210
end
@@ -204,4 +215,4 @@ function (ss::SummarySize)(obj::Task)
204215
return size
205216
end
206217

207-
(ss::SummarySize)(obj::BigInt) = _summarysize(ss, obj) + obj.alloc*sizeof(Base.GMP.Limb)
218+
(ss::SummarySize)(obj::BigInt) = _summarysize(ss, obj, ss.count) + obj.alloc * (ss.count ? 1 : sizeof(Base.GMP.Limb))

0 commit comments

Comments
 (0)