Freeing memory withing struct

memory-allocation
#1
julia> mutable struct S
           x::Vector{Matrix{Float64}}
           S() = new()
       end

julia> s = S();

julia> s
S(#undef)

julia> s.x
ERROR: UndefRefError: access to undefined reference
Stacktrace:
 [1] getproperty(::Any, ::Symbol) at .\sysimg.jl:18

julia> s.x = [rand(1000,1000) for i in 1:100]; # takes up memory

Initially the field x is undefined (taking up basically no memory). After the assignment, some piece of memory is assigned to s.x. Is there a nice way to free up this memory again, that is, for example, get s.x back to be #undef?

Outside of a struct, the typical answer seems to be set x = nothing and perhaps call GC.gc() to free the memory. But this doesn’t work for a field of a struct as the memory could still be accessed (it’s still reachable).

I can set s.x = Matrix{Float64}[] and call GC.gc() to free the memory. I’m just wondering if there is a better approach.

To give you some context, I have a couple of costly operations that I perform in-place in some preallocated memory. To that end I have a struct, say Preallocations, whose fields are being used as preallocated memory. However, the memory assigned to some of those fields might be very large and only needed in some operations. After those operations finish I’d like to free the memory again.

Thanks in advance.

0 Likes

#2

That is not possible.

For your example, why not just de-reference the Preallocations instance? Say by letting it go out of scope?

0 Likes

#3

You can use ::Union{Nothing, Vector{Matrix{Float64}}}. IIUC its cost will be ~1 byte, and the performance impact should be negligible for most use cases.

1 Like

#4

sizehint!(s.x, 0) works even with non mutable structs.

3 Likes

#5

A different variant is s.x=Vector{Matrix{Float64}}[]. If you store many S instances and don’t want to pay the jl_array memory price, it might be advisable to have some const _nil = Vector{Matrix{Float64}}[] and set s.x = _nil.

Then you can also initialize with _nil, and check for initialized-ness by s.x === _nil (triple ===, about as fast as issassigned(s, :x) and much faster than == because you avoid dereferencing the pointer).

By making the field s.x always assigned (by not having an inner constructor) you speed up every single access (if the inner constructor permits unassigned s.x then julia emits nullpointer checks on access).

Downside: If your code is buggy, then someone can forget the s.x === _nil check and actually mutate the global _nil. Nobody will protect you from this bug, because you intentionally removed all guardrails.

If you care more about maintainability than raw speed, then Union{Nothing, Vector{Matrix{Float64}}} is probably more appropriate.

1 Like

#6

Preallocations would also hold preallocations for other operations so de-referencing the instance doesn’t work.

Thanks, but I’d like to avoid unions here, mainly for readability reasons.

sizehint! doesn’t seem to give the desired effect. However, resize!(s.x, 0) does. Thanks.

0 Likes

#7

Preallocations would also hold preallocations for other operations

Use separate ones?

I think Kristoffer means you should use:

empty!(s.x)
sizehint!(s.x, 0)

as otherwise the “reserve” size for s.x stays at the large size and occupies memory (I think).

0 Likes