Mutable struct vs Ref in an immutable one?

I want to define a struct where one field is mutable, and one isn’t,. I see two ways to approach this:

struct A
     x :: Int
     y :: typeof(Ref(1))
end

and

mutable struct B
     const x :: Int
     y :: Int
end

What are the differences between the two from a compiler optimization point-of-view? Which style is to be preferred in what situation?

4 Likes

I’m pretty sure I saw Jameson Nash recommend the second approach (using mutable struct), but unfortunately I can’t find a reference right now.

The const annotation requires Julia 1.8. Which one to use is depend on how you want to use them. For struct A the access y[] will almost always require a pointer dereference. B has a nicer layout in memory and the compiler can load both x and y simultaneously.

A downside is the layout when the A and B are contained in other structures. As an example inside an array A can be stored inline whereas for B a reference will be stored.

4 Likes

@vchuravy This touches on a question I asked some time ago about determining when a type is stored inline in an array versus as pointers.

I understood that only isbits types were stored inline, and that therefore in this case Struct A would not be stored inline.

Is this wrong? And if so (as I asked in the other thread), how can I determine when a type will be stored inline or not?

Mutable types cannot be stored inline. This is why B is superior to A, since A has to have a costly indirect reference y allocated elsewhere the heap, while B is a cheap simple pointer to fast immediate heap memory. The const is a bit of a red-herring there, since generally it has no performance impact, but just clarifies intent and invariants.

1 Like

There are lots of comments elsewhere that stated storing something inline in an array (or on the stack, for that matter) required the type to be isbits, which is a strictly stronger requirement than being simply immutable. My previous understanding was that an immutable struct with any GC’ed element could not be stored contiguously in an array.

However, my own tests just now using pointer() to count the spacing between addresses to array elements confirm that struct A in the above example is stored contiguously.

Is this a change in more recent versions of Julia? Is it solely a property of the mutability of the type that allows/forbids it to be stored contiguously?

1 Like

what about making the mutable field a separate mutable struct ?
e.g.

mutable struct Bx
    x::Int
end

struct B
     x::Bx
     y::Int
end

b = B(Bx(1), 2)
@show b
# b = B(Bx(1), 2)

b.x.x = 2
@show b
# b = B(Bx(2), 2)

what would that mean performance and memory wise ?

How’s that different from using a RefValue field in an immutable struct, which is what was suggested in the original post already?