Skip to content

Commit 7267793

Browse files
topolarityvtjnash
andauthored
Base: Add count argument to Base.summarysize (#58874)
In many cases, number of allocations is more important than memory allocated (w.r.t. performance). 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. --------- Co-authored-by: Jameson Nash <[email protected]>
1 parent ad39f65 commit 7267793

File tree

1 file changed

+32
-21
lines changed

1 file changed

+32
-21
lines changed

base/summarysize.jl

Lines changed: 32 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,21 @@ 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 the number of allocations they span.
1820
1921
# Keyword Arguments
22+
- `count`: if false, return the total size of the objects in memory. if true, return the
23+
number of allocations spanned by the object.
2024
- `exclude`: specifies the types of objects to exclude from the traversal.
2125
- `chargeall`: specifies the types of objects to always charge the size of all of their
2226
fields, even if those fields would normally be excluded.
@@ -33,28 +37,32 @@ julia> Base.summarysize(Ref(rand(100)))
3337
3438
julia> sizeof(Ref(rand(100)))
3539
8
40+
41+
julia> Base.summarysize(Core.svec(1.0, "testing", true); count=true)
42+
4
3643
```
3744
"""
3845
function summarysize(obj;
46+
count::Bool = false,
3947
exclude = Union{DataType, Core.TypeName, Core.MethodInstance},
4048
chargeall = Union{Core.TypeMapEntry, Method})
4149
@nospecialize obj exclude chargeall
42-
ss = SummarySize(IdDict(), Any[], Int[], exclude, chargeall)
50+
ss = SummarySize(IdDict(), Any[], Int[], exclude, chargeall, count)
4351
size::Int = ss(obj)
4452
while !isempty(ss.frontier_x)
4553
# DFS heap traversal of everything without a specialization
4654
# BFS heap traversal of anything with a specialization
4755
x = ss.frontier_x[end]
4856
i = ss.frontier_i[end]
4957
val = nothing
50-
if isa(x, SimpleVector)
58+
if isa(x, Core.SimpleVector)
5159
nf = length(x)
5260
if isassigned(x, i)
5361
val = x[i]
5462
end
5563
elseif isa(x, GenericMemory)
5664
T = eltype(x)
57-
if Base.allocatedinline(T)
65+
if allocatedinline(T)
5866
np = datatype_npointers(T)
5967
nf = length(x) * np
6068
idx = (i-1) ÷ np + 1
@@ -90,9 +98,9 @@ function summarysize(obj;
9098
return size
9199
end
92100

93-
(ss::SummarySize)(@nospecialize obj) = _summarysize(ss, obj)
101+
(ss::SummarySize)(@nospecialize obj) = _summarysize(ss, obj, ss.count)
94102
# define the general case separately to make sure it is not specialized for every type
95-
@noinline function _summarysize(ss::SummarySize, @nospecialize obj)
103+
@noinline function _summarysize(ss::SummarySize, @nospecialize(obj), count::Bool)
96104
issingletontype(typeof(obj)) && return 0
97105
# NOTE: this attempts to discover multiple copies of the same immutable value,
98106
# and so is somewhat approximate.
@@ -112,7 +120,7 @@ end
112120
# 0-field mutable structs are not unique
113121
return gc_alignment(0)
114122
end
115-
return sz
123+
return count ? 1 : sz
116124
end
117125

118126
(::SummarySize)(obj::Symbol) = 0
@@ -121,14 +129,13 @@ end
121129
function (ss::SummarySize)(obj::String)
122130
key = ccall(:jl_value_ptr, Ptr{Cvoid}, (Any,), obj)
123131
haskey(ss.seen, key) ? (return 0) : (ss.seen[key] = true)
124-
return Core.sizeof(Int) + Core.sizeof(obj)
132+
return (ss.count ? 1 : (Core.sizeof(Int) + Core.sizeof(obj)))
125133
end
126134

127135
function (ss::SummarySize)(obj::DataType)
128136
key = pointer_from_objref(obj)
129137
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)
138+
size::Int = ss.count ? 1 : sizeof(DataType)
132139
size += ss(obj.parameters)::Int
133140
if isdefined(obj, :types)
134141
size += ss(obj.types)::Int
@@ -139,30 +146,34 @@ 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
154+
headersize = 2 * sizeof(Int)
155+
size::Int = (ss.count ? 1 : headersize)
149156
datakey = 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) + 16 != datakey
162+
size += 1
163+
end
153164
T = eltype(obj)
154-
if !isempty(obj) && T !== Symbol && (!Base.allocatedinline(T) || (T isa DataType && !Base.datatype_pointerfree(T)))
165+
if !isempty(obj) && T !== Symbol && (!allocatedinline(T) || (T isa DataType && !datatype_pointerfree(T)))
155166
push!(ss.frontier_x, obj)
156167
push!(ss.frontier_i, 1)
157168
end
158169
end
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) + (ss.count ? 1 : obj.alloc * sizeof(GMP.Limb))

0 commit comments

Comments
 (0)