# What is the memory overhead of a mutable struct?

**URL:** <https://discourse.julialang.org/t/what-is-the-memory-overhead-of-a-mutable-struct/26963>\
**Category:** Performance\
**Created:** [July 30, 2019, 3:25am UTC](https://discourse.julialang.org/t/what-is-the-memory-overhead-of-a-mutable-struct/26963 "2019-07-30T03:25:46Z")\
**Posts on this page:** 9\
**Page:** 1

<div class="post-metadata">

**Author:** ![Russ](https://avatars.discourse-cdn.com/v4/letter/r/e36b37/32.png) [@Russ](https://discourse.julialang.org/u/Russ)\
**Post date:** [July 30, 2019, 3:25am UTC](https://discourse.julialang.org/t/what-is-the-memory-overhead-of-a-mutable-struct/26963/1 "2019-07-30T03:25:46Z")

</div>

I’m trying to figure out how much memory a mutable struct actually takes up. For example, in Java or C#, there’s a vptr and a syncroot/hash id, so the overhead is 8 or 16 bytes, depending on if it’s being run in 32 or 64-bit. Julia’s `sizeof()` seems to only return the size of the user-defined fields and does not include the object header. I’d also be interested to know what is stored in the header, just out of curiosity. Thanks!

---

<div class="post-metadata">

**Author:** ![carstenbauer](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/carstenbauer/32/4981_2.png) [@carstenbauer](https://discourse.julialang.org/u/carstenbauer)\
**Post date:** [July 30, 2019, 5:49am UTC](https://discourse.julialang.org/t/what-is-the-memory-overhead-of-a-mutable-struct/26963/2 "2019-07-30T05:49:45Z")

</div>

Perhaps `Base.summarysize` is what you’re looking for. (EDIT: I guess it’s not.)

---

<div class="post-metadata">

**Author:** ![foobar\_lv2](https://avatars.discourse-cdn.com/v4/letter/f/ee59a6/32.png) [@foobar\_lv2](https://discourse.julialang.org/u/foobar_lv2)\
**Post date:** [July 30, 2019, 10:17am UTC](https://discourse.julialang.org/t/what-is-the-memory-overhead-of-a-mutable-struct/26963/3 "2019-07-30T10:17:23Z")

</div>

I think the following should include all overheads:

```julia
julia> function bar(arr::Vector{Base.RefValue{T}}) where {T}
       for i=1:length(arr)
       arr[i]=Ref{T}()
       end
       nothing
       end
julia> for N=1:32
       T=NTuple{N, UInt32}
       arr=[Ref{T}() for i=1:1024]
       bar(arr)
       bar(arr)
       alloc = (@allocated bar(arr))/1024
       println("Object with 4 byte align, size $(N*4) takes $alloc bytes")
       end
Object with 4 byte align, size 4 takes 16.0 bytes
Object with 4 byte align, size 8 takes 16.0 bytes
Object with 4 byte align, size 12 takes 32.0 bytes
Object with 4 byte align, size 16 takes 32.0 bytes
Object with 4 byte align, size 20 takes 32.0 bytes
Object with 4 byte align, size 24 takes 32.0 bytes
Object with 4 byte align, size 28 takes 48.0 bytes
Object with 4 byte align, size 32 takes 48.0 bytes
Object with 4 byte align, size 36 takes 48.0 bytes
Object with 4 byte align, size 40 takes 48.0 bytes
Object with 4 byte align, size 44 takes 64.0 bytes
Object with 4 byte align, size 48 takes 64.0 bytes
Object with 4 byte align, size 52 takes 64.0 bytes
Object with 4 byte align, size 56 takes 64.0 bytes
Object with 4 byte align, size 60 takes 80.0 bytes
Object with 4 byte align, size 64 takes 80.0 bytes
Object with 4 byte align, size 68 takes 80.0 bytes
Object with 4 byte align, size 72 takes 80.0 bytes
Object with 4 byte align, size 76 takes 96.0 bytes
Object with 4 byte align, size 80 takes 96.0 bytes
Object with 4 byte align, size 84 takes 96.0 bytes
Object with 4 byte align, size 88 takes 96.0 bytes
Object with 4 byte align, size 92 takes 112.0 bytes
Object with 4 byte align, size 96 takes 112.0 bytes
Object with 4 byte align, size 100 takes 112.0 bytes
Object with 4 byte align, size 104 takes 112.0 bytes
Object with 4 byte align, size 108 takes 128.0 bytes
Object with 4 byte align, size 112 takes 128.0 bytes
Object with 4 byte align, size 116 takes 128.0 bytes
Object with 4 byte align, size 120 takes 128.0 bytes
Object with 4 byte align, size 124 takes 144.0 bytes
Object with 4 byte align, size 128 takes 144.0 bytes

```

---

<div class="post-metadata">

**Author:** ![WschW](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/wschw/32/6575_2.png) [@WschW](https://discourse.julialang.org/u/WschW)\
**Post date:** [July 30, 2019, 3:49pm UTC](https://discourse.julialang.org/t/what-is-the-memory-overhead-of-a-mutable-struct/26963/4 "2019-07-30T15:49:17Z")

</div>

According to [https://pkg.julialang.org/docs/julia/THl1k/1.1.1/devdocs/object.html](https://pkg.julialang.org/docs/julia/THl1k/1.1.1/devdocs/object.html) tuples have a unique representation, so the prior example may not universally apply. From the documentation it appears that a Julia struct itself does not contain extra fields but that it is wrapped in a c struct that contains GC and type information.

---

<div class="post-metadata">

**Author:** ![Russ](https://avatars.discourse-cdn.com/v4/letter/r/e36b37/32.png) [@Russ](https://discourse.julialang.org/u/Russ)\
**Post date:** [July 30, 2019, 5:18pm UTC](https://discourse.julialang.org/t/what-is-the-memory-overhead-of-a-mutable-struct/26963/5 "2019-07-30T17:18:06Z")

</div>

Thanks for the input everyone, it seems like we still don’t have a really concrete answer yet. I did a very unscientific test where I created 100M structs and put them in an array:

```julia
mutable struct MemTester
    i::Int64
end

testers = Array{MemTester}(undef, 100000000)

function memoryTest()
    for i = 1:100000000
        testers[i] = MemTester(i)
    end
end

```

I ran this and looked at the total process memory usage on a 64 bit REPL, which was roughly 3.2GB. That’s 32 bytes per item. We are keeping them in an array to avoid the GC collecting them, so figure 8 bytes per item pointer. And then there’s an 8 byte Int64 user field per object, so that leaves 16 bytes for the header. In general, I don’t trust OS heap allocation measurements to be particularly accurate, but that’s at least a fairly decent guess. I think.

EDIT: This is wrong, I’m using a non-const array which causes extra allocations, see below 😁

---

<div class="post-metadata">

**Author:** ![foobar\_lv2](https://avatars.discourse-cdn.com/v4/letter/f/ee59a6/32.png) [@foobar\_lv2](https://discourse.julialang.org/u/foobar_lv2)\
**Post date:** [July 30, 2019, 5:43pm UTC](https://discourse.julialang.org/t/what-is-the-memory-overhead-of-a-mutable-struct/26963/6 "2019-07-30T17:43:42Z")

</div>

Your `memoryTest()` needs access to the global non-const object `Main.testers`. This causes additional allocations. Use `@timev` after warm-up to see both the number of allocs and the number of allocated bytes, and divide to obtain the mean size of allocated objects. With your code, I get 16 bytes/object and 1.5 allocations per iteration. After inspecting @code\_native, this is surprisingly not due to loop unrolling; instead there must be some optimization for `jl_box_int64` going on.

---

<div class="post-metadata">

**Author:** ![Russ](https://avatars.discourse-cdn.com/v4/letter/r/e36b37/32.png) [@Russ](https://discourse.julialang.org/u/Russ)\
**Post date:** [July 30, 2019, 6:02pm UTC](https://discourse.julialang.org/t/what-is-the-memory-overhead-of-a-mutable-struct/26963/7 "2019-07-30T18:02:26Z")

</div>

Hmm… I figured it was preallocated, so it wouldn’t cause additional allocations. Is that not how arrays work? Why would array accesses cause allocations?

---

<div class="post-metadata">

**Author:** ![foobar\_lv2](https://avatars.discourse-cdn.com/v4/letter/f/ee59a6/32.png) [@foobar\_lv2](https://discourse.julialang.org/u/foobar_lv2)\
**Post date:** [July 30, 2019, 6:16pm UTC](https://discourse.julialang.org/t/what-is-the-memory-overhead-of-a-mutable-struct/26963/8 "2019-07-30T18:16:10Z")

</div>

The issue is the call `setindex!(testers, MemTester(i), i)`, i.e. `testers[i] = MemTester(i)`. The first argument is of unknown type (because it is a global variable), therefore julia needs to call into the runtime: It calls a C function that walks the method table and figures out whom to call.

I think the issue is that this C function expects arguments that are pointers to valid heap-allocated julia objects and then extracts the argument types from their headers. We already have a pointer to `MemTester(i)`, but we also need a pointer to an object that contains the integer `i` and an object header that tells the runtime that it is an integer. We obtain this object via `jl_box_int64`. That appears to allocate sometimes.

TLDR: Don’t use non-const globals. Really, don’t.

---

<div class="post-metadata">

**Author:** ![Russ](https://avatars.discourse-cdn.com/v4/letter/r/e36b37/32.png) [@Russ](https://discourse.julialang.org/u/Russ)\
**Post date:** [July 30, 2019, 6:24pm UTC](https://discourse.julialang.org/t/what-is-the-memory-overhead-of-a-mutable-struct/26963/9 "2019-07-30T18:24:21Z")

</div>

Ohhh I had no idea about the global const stuff. Thanks for that.

So, yes, you’re right, after running the experiment again, it’s 16 bytes per object, so only an 8 byte header. Nice!
