Understanding how an array of structures works

A pointer assumes that the object lies somewhere in memory which might not be true. The object might not be created at all.

How can you tell the difference? If it is isbits it is stored inline. What you take out from the array can be a pointer or a copy or something, since its immutable it doesn’t matter.

2 Likes

Uhm… I cannot imagine an example of that, but I will try to figure that out. Only if the object is never used and the compiler makes the decision of not creating it, I don’t know.

EDIT: I understand that one case where it does make sense for an immutable object not to have pointer is, specifically, when it is part of a larger array which itself has one pointer. This makes sense.

Clearly it doesn’t matter for the use of the data, but if it might be a pointer, that means that the vector y of the example is not contiguous in memory. I was reasoning that a vector of immutable structs is in principle allocated when it is created and, thus, possibly contiguous, while the vector of mutable types clearly isn’t.

(By the way: I found now this interest post that might be of interest of people reaching here: http://schurkus.com/2018/01/09/mutability-and-performance-in-julia/ - which made me think that the references to immutable objects in a vector may or may not be copies, and the compiler will decide that trying to optimize performance).

Ps. 2. : Thank you very much kristoffer.carlsson for even taking your time to read this… Julia has an interesting peculiarity that the people answering the questions here are so deeply involved in the language that even very simple questions frequently derive into deep discussions on more profound aspects of the language design.

For example, consider:

f(z) = 2z^2 - 3z + 2

and you call f(3+4im) — i.e. you pass a Complex{Int}, an immutable struct comprising its real and imaginary Int parts. The intermediate computations like 2z^2 are also Complex{Int} values, but the compiler never allocates a struct of them anywhere (either on the heap or on the stack) — instead, these computations are all inlined and the real and imaginary parts of all of the intermediate values are stored separately in CPU registers (which don’t have a pointer address).

(You can see this explicitly if you do @code_native f(3+4im) — only the movq instructions at the beginning and the end load/store explicit Complex{Int} values from/to memory for the input and final output values, respectively.)

9 Likes

I guess the question is what you mean with y[i]. Do you mean what is returned from calling it or what is stored at offset i in y?

I meant what is stored in offest i in y

(although I have to be honest in that I only realize that there is a difference between these two things now).

Okay, what’s actually stored there (for isbits struct) is always the data itself. So y[1] = i would have to copy the data from i into y[1].

3 Likes

So, getting back to my original issue: How does one go about creating a dense array of structures in julia?

(I’m looking for sequential memory assignments similar to a struct of structs in C)

Nothing special is required, Array{T}(undef, dims) should work like for all other types.

3 Likes

With the restriction that T has to be isbitstype.

3 Likes

You can verify yourself, for example:

julia> struct Bar
           a::Char
       end

julia> struct Foo
           w::Float64
           y::Int
           x::Tuple{Int32,UInt32}
           z::Bar
       end

julia> a = Array{Foo}(undef, 10);

julia> a[2]
Foo(6.90014562949905e-310, 37, (11, 0x00000000), Bar('\x3e\xa1\xcf\xd0'))

julia> unsafe_load(pointer(a), 2)
Foo(6.90014562949905e-310, 37, (11, 0x00000000), Bar('\x3e\xa1\xcf\xd0'))

julia> unsafe_load(pointer(a) + sizeof(Foo))
Foo(6.90014562949905e-310, 37, (11, 0x00000000), Bar('\x3e\xa1\xcf\xd0'))

julia> a[3]
Foo(2.4e-322, 3, (1050791888, 0x00007f05), Bar('\x00\x00\x00\x35'))

julia> unsafe_load(pointer(a), 3)
Foo(2.4e-322, 3, (1050791888, 0x00007f05), Bar('\x00\x00\x00\x35'))

julia> unsafe_load(pointer(a) + (3-1)*sizeof(Foo))
Foo(2.4e-322, 3, (1050791888, 0x00007f05), Bar('\x00\x00\x00\x35'))
3 Likes

Ok, I understand that if we want an array of mutable structs, the contiguous array will contain pointers to the structs. The struct itself is somewhere on the heap.

This can have performance implications due to the extra pointer lookup as well the resulting as non-sequential memory access.

Is it possible to have an array of mutable structures, but the structures are laid out contiguously in memory?

Currently, no.

1 Like