If you look how vectors are stored, this is understandable. A Vector{Int}
is really a struct:
julia> dump(Vector{Int})
mutable struct Vector{Int64} <: DenseVector{Int64}
ref::MemoryRef{Int64}
size::Tuple{Int64}
And a MemoryRef{Int}
is also a struct:
julia> dump(MemoryRef{Int})
struct MemoryRef{Int64} <: Ref{Int64}
ptr_or_offset::Ptr{Nothing}
mem::Memory{Int64}
And so is Memory{Int}
:
julia> dump(Memory{Int})
mutable struct Memory{Int64} <: DenseVector{Int64}
const length::Int64
const ptr::Ptr{Nothing}
In addition comes the actual memory for the data, i.e. the chunk of memory pointed to by the ptr
in the Memory
struct. The structs also have their type information encoded, at least a pointer to a static DataType
struct, that’s 8 bytes.
When you create a
as a = [2]
, the data buffer has only room for 1 Int
, but if you push more to the vector it’s reallocated to make room for more:
julia> a = [2]
1-element Vector{Int64}:
2
julia> a.ref.mem.length
1
julia> @allocated push!(a, 1)
96
julia> a.ref.mem.length
8
julia> @allocated push!(a, 1)
0
julia> a.ref.mem.length
8
Removing from the front of the vector just changes the ptr_or_offset
in the MemoryRef
:
julia> a.ref.ptr_or_offset
Ptr{Nothing}(0x00007f73096751c0)
julia> popfirst!(a)
2
julia> a.ref.ptr_or_offset
Ptr{Nothing}(0x00007f73096751c8)
It’s also possible to get hold of the DataType
pointer which is at the word before the Vector{Int}
object, but the lower 4 bits are used temporarily by the garbage collector, and the rest doubles as different types of data, so this is highly unsafe, and easily gives segfaults:
julia> unsafe_load(Ptr{DataType}(unsafe_load(Ptr{UInt}(pointer_from_objref(a)), 0) & ~UInt(0xf)))
Array{Int64, 1}