# Immutable vs Type: reassignment via arrays and performance

**URL:** https://discourse.julialang.org/t/immutable-vs-type-reassignment-via-arrays-and-performance/3364
**Category:** General Usage
**Tags:** question
**Created:** [April 25, 2017, 2:51am UTC](https://discourse.julialang.org/t/immutable-vs-type-reassignment-via-arrays-and-performance/3364 "2017-04-25T02:51:25Z")
**Posts on this page:** 12
**Page:** 1

<div class="post-metadata">

### Author: ![mcovalt](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mcovalt/32/1722_2.png) [@mcovalt](https://discourse.julialang.org/u/mcovalt)
#### Post date: [April 25, 2017, 2:51am UTC](https://discourse.julialang.org/t/immutable-vs-type-reassignment-via-arrays-and-performance/3364/1 "2017-04-25T02:51:25Z")

</div>

According to the docs, operating on immutable types can result in increased performance. On first thought, I assumed this was because the data contained inside immutable objects is known to be unchanging, but upon tinkering a bit, I realized immutable data can be changed when the data lies in an array:

```julia
immutable Test
    x1::Float64
    x2::Array{Float64,1}
end

test = Test(1.0,[1.0])
# This is an invalid operation
test.x1 = 2.0
# This is a valid operation
test.x2[1] = 2.0

```

So my questions are:

- Is reassignment to an array inside an immutable a bad thing?
- If performance is better with immutable types and an array structure is always the same, why not always use immutables at the cost of less clean code?

---

<div class="post-metadata">

### Author: ![ChrisRackauckas](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/chrisrackauckas/32/77_2.png) [@ChrisRackauckas](https://discourse.julialang.org/u/ChrisRackauckas)
#### Post date: [April 25, 2017, 3:35am UTC](https://discourse.julialang.org/t/immutable-vs-type-reassignment-via-arrays-and-performance/3364/2 "2017-04-25T03:35:49Z")

</div>

> [@mcovalt](#):
>
> Is reassignment to an array inside an immutable a bad thing?

It’s fine.

> [@mcovalt](#):
>
> If performance is better with immutable types and an array structure is always the same, why not always use immutables at the cost of less clean code?

Because sometimes you want to change the pointer. But yes, `immutable`s as a wrapper for arrays is a very common “trick”. When you do this, you can make them dispatch and broadcast differently. This kind of wrapper type is actually the suggested implementation for `@threads` broadcasting.

> <https://github.com/JuliaLang/julia/issues/19777#issuecomment-278852825>
>
> It would be nice to be able to put \`@threads\` in front of a dot call, e.g. \`@thr…eads X .= f.(Y)\`, and have it call a multi-threaded version of \`broadcast\` (which would assume \`f\` is thread-safe).

> [@mcovalt](#):
>
> According to the docs, operating on immutable types can result in increased performance. On first thought, I assumed this was because the data contained inside immutable objects is known to be unchanging

Immutables are value-types. It’s easiest for me to understand this in contract to mutable types which are not. Mutable types are pointers to each field. So each access has an indirection, and this can ruin optimizations and cause other performance issues (in reality, if it’s not a tight loop, you won’t notice this). Arrays of mutables are arrays of pointers.

Value types have their values “inlined”, so an array of immutables is not an array of pointers, it’s an array of larger pieces of data all written in a line. If an array of immutables concretely typed and you want to grab `A[i].b`, the compiler can know exactly how many bits to move down the memory to get the value (not just the pointer). So an immutable is almost like a larger primitative type which has a tag in the front to denote what it is and what’s in there.

Arrays are mutable types. So when you put an array in an immutable, the field `A.b` is a pointer to the array. So there’s one level of indirection. But if `A` was mutable, it would be two pointers that would have to be dereferenced. But one level of indirection is enough to get rid of many optimizations, so that’s why you won’t notice too much of a difference unless you’re in a tight loop. And in the case where you have immutables, many times the compiler will compile away the “immutable” and turn it into a “zero-cost abstraction”.

I might have some of the details a little bit off, so someone else will probably want to clean this up, but that’s the general idea.

---

<div class="post-metadata">

### Author: ![evanfields](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/evanfields/32/1744_2.png) [@evanfields](https://discourse.julialang.org/u/evanfields)
#### Post date: [April 25, 2017, 4:03am UTC](https://discourse.julialang.org/t/immutable-vs-type-reassignment-via-arrays-and-performance/3364/3 "2017-04-25T04:03:09Z")

</div>

I’ll give a stab at answer this. I want to be upfront that I’m not an expert Julia user; hopefully I can shed some light, but this is as much to educate myself as anything else. Hopefully someone who actually knows what they’re talking about can correct any mistakes.

1. immutable types can be bits types:

```julia
julia> type MutFloat
           x::Float64
       end

julia> immutable ImmFloat
           x::Float64
       end

julia> isbits(MutFloat)
false

julia> isbits(ImmFloat)
true

```

1. Let’s compare the performance of these two types. Additionally, we’ll include an immutable type wrapping an array.

```julia
immutable ArrFloat
	x::Vector{Float64}
end

floats = rand(100000)
immfloats = map(ImmFloat, floats)
mutfloats = map(MutFloat, floats)
arrfloats = map(x -> ArrFloat([x]), floats)

println("Immutable float")
println(@benchmark sum(t.x for t in immfloats))
println("Mutable float")
println(@benchmark sum(t.x for t in mutfloats))
println("Immutable-wrapped array")
println(@benchmark sum(t.x[1] for t in arrfloats))

Immutable float
Trial(97.806 μs)
Mutable float
Trial(110.693 μs)
Immutable-wrapped array
Trial(936.762 μs)

```

So empirically, there is a slight performance advantage to have immutables. Wrapping in an array is really bad for performance.

What’s going on here? I think with an immutable whose fields are a known leaf type - like `ImmFloat` in our example - the data passed around are exactly the bits of the fields. So passing an `ImmFloat` is just passing the 64 bits that make up a `Float64`; there are not references or redirection. Therefore, if someone gives you an `ImmFloat`, operating on its value is easy: just look at the bits they handed you.

For mutable types, however, we pass around references. (From the manual: “An object with an immutable type is passed around (both in assignment statements and in function calls) by copying, whereas a mutable type is passed around by reference.”) So if I hand you a `MutFloat`, you’re not holding the bits of the float itself; you’re holding the address where those bits can be found. To operate on the `MutFloat`, you have to go where the address tells you to find the bits. This is slower than the corresponding operation on a `ImmFloat`.

In the last case, what happens when I give you an immutable type holding an array? I’ve given you exactly the address to that array (whereas if I had given you a mutable type, you’d be holding the address to look for the array’s address). But to actually get the `Float64` data out of the array, you have to call `getindex` which is comparatively slow - bounds checking, function call, etc.

So punchline here is if you want mutability, use a mutable type.

1. I think there may be psychological benefits to immutable types. We tend to use immutables when we’ve carefully thought about what types our data will have and which parts of our programs are invariant. Type specificity is really important for performance:

```julia
immutable ImmFloatInt
	x::Union{Float64, Int}
end
immfloatints = map(ImmFloatInt, floats)
println("Immutable union")
println(@benchmark sum(t.x for t in immfloatints))

Immutable union
Trial(3.453 ms)

```

Here, adding up the same 100,000 floating point numbers is about 35x faster if they’re held in `ImmFloats` rather than `ImmFloatInts`, the latter of which has a union type. (I believe union performance may be improving in the near future, not sure; this is on Julia 0.5.1.)

---

<div class="post-metadata">

### Author: ![ChrisRackauckas](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/chrisrackauckas/32/77_2.png) [@ChrisRackauckas](https://discourse.julialang.org/u/ChrisRackauckas)
#### Post date: [April 25, 2017, 4:32am UTC](https://discourse.julialang.org/t/immutable-vs-type-reassignment-via-arrays-and-performance/3364/4 "2017-04-25T04:32:32Z")

</div>

> [@evanfields](#):
>
> So empirically, there is a slight performance advantage to have immutables. Wrapping in an array is really bad for performance.

I didn’t catch that he was wanting to use an array just to make a “mutable field”. Yeah, don’t do that. However, your example probably wouldn’t be quite as bad if you added `@inbounds`. But `Ref` exists for this reason.

---

<div class="post-metadata">

### Author: ![evanfields](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/evanfields/32/1744_2.png) [@evanfields](https://discourse.julialang.org/u/evanfields)
#### Post date: [April 25, 2017, 4:44am UTC](https://discourse.julialang.org/t/immutable-vs-type-reassignment-via-arrays-and-performance/3364/5 "2017-04-25T04:44:35Z")

</div>

I figured `@inbounds` would help, but I couldn’t find where to stick it and get any improvement. (Maybe it’s just a limitation of using a generator expression inside the sum?)

```julia
julia> @benchmark sum(t.x[1] for t in arrfloats)
BenchmarkTools.Trial:
  memory estimate: 48 bytes
  allocs estimate: 3
  --------------
  minimum time: 936.763 μs (0.00% GC)
  median time: 965.840 μs (0.00% GC)
  mean time: 978.484 μs (0.00% GC)
  maximum time: 1.552 ms (0.00% GC)
  --------------
  samples: 4923
  evals/sample: 1

julia> @benchmark @inbounds sum(t.x[1] for t in arrfloats)
BenchmarkTools.Trial:
  memory estimate: 48 bytes
  allocs estimate: 3
  --------------
  minimum time: 936.433 μs (0.00% GC)
  median time: 966.832 μs (0.00% GC)
  mean time: 979.603 μs (0.00% GC)
  maximum time: 1.595 ms (0.00% GC)
  --------------
  samples: 4934
  evals/sample: 1

julia> @benchmark sum(@inbounds t.x[1] for t in arrfloats)
ERROR: syntax: unexpected ")"

```

---

<div class="post-metadata">

### Author: ![ChrisRackauckas](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/chrisrackauckas/32/77_2.png) [@ChrisRackauckas](https://discourse.julialang.org/u/ChrisRackauckas)
#### Post date: [April 25, 2017, 4:50am UTC](https://discourse.julialang.org/t/immutable-vs-type-reassignment-via-arrays-and-performance/3364/6 "2017-04-25T04:50:36Z")

</div>

> [@evanfields](#):
>
> @benchmark sum(@inbounds t.x[1] for t in arrfloats)

In `@inbounds` doesn’t propagate through function calls which aren’t inlined, so it has to be this one. But you don’t want the full expression to be captured, so it should just be

```julia
@benchmark sum(@inbounds(t.x[1]) for t in arrfloats)

```

---

<div class="post-metadata">

### Author: ![evanfields](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/evanfields/32/1744_2.png) [@evanfields](https://discourse.julialang.org/u/evanfields)
#### Post date: [April 25, 2017, 4:57am UTC](https://discourse.julialang.org/t/immutable-vs-type-reassignment-via-arrays-and-performance/3364/7 "2017-04-25T04:57:27Z")

</div>

Doesn’t seem to work:

````julia
ERROR: MethodError: no method matching +(::Void, ::Void)
Closest candidates are:
  +(::Any, ::Any, ::Any, ::Any...) at operators.jl:138
 in mapfoldl_impl(::Base.#identity, ::Base.#+, ::Void, ::Base.Generator{Array{ArrFloat,1},##36#38}, ::Int64) at .\reduce.jl:46```
````

---

<div class="post-metadata">

### Author: ![ChrisRackauckas](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/chrisrackauckas/32/77_2.png) [@ChrisRackauckas](https://discourse.julialang.org/u/ChrisRackauckas)
#### Post date: [April 25, 2017, 5:20am UTC](https://discourse.julialang.org/t/immutable-vs-type-reassignment-via-arrays-and-performance/3364/8 "2017-04-25T05:20:20Z")

</div>

Yeah, peculiar. I can’t find out how to `@inbounds` the generator either. Also, the ref form

```julia
immutable RefFloat
	x::Ref{Float64}
end
reffloats = map(x -> RefFloat(Ref{Float64}(x)), floats)
println(@benchmark sum(t.x[] for t in arrfloats))

```

is being slower than the array form, which contradicts things I’ve seen before. Ehh I’m off today.

---

<div class="post-metadata">

### Author: ![vchuravy](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/vchuravy/32/8_2.png) [@vchuravy](https://discourse.julialang.org/u/vchuravy)
#### Post date: [April 25, 2017, 5:22am UTC](https://discourse.julialang.org/t/immutable-vs-type-reassignment-via-arrays-and-performance/3364/9 "2017-04-25T05:22:24Z")

</div>

`Ref` is an abstract type you probably want `RefValue` here.

---

<div class="post-metadata">

### Author: ![jameson](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/jameson/32/23_2.png) [@jameson](https://discourse.julialang.org/u/jameson)
#### Post date: [April 25, 2017, 5:58pm UTC](https://discourse.julialang.org/t/immutable-vs-type-reassignment-via-arrays-and-performance/3364/10 "2017-04-25T17:58:02Z")

</div>

> [@ChrisRackauckas](#):
>
> But Ref exists for this reason

Not to be overly pedantic, but that’s not why Ref was added. It was specifically added to act like a `view` operation on one element, primarily for aiding in C-interop. In fact, it is an abstract type specifically because that allows it to perform its intended function (despite also making it sub-optimal as a annotation for making a field mutable).

From a performance standpoint, it’s usually best if you make the whole object immutable, or the whole object mutable. It’s better (performance and memory) to wrap an entire immutable object in a mutable object (including in a mutable struct or an Array), rather than using Ref (or RefValue or Array or another mutable type) to select multiple specific fields to make mutable. This flattens out the memory hierarchy, meaning the computer needs to do fewer memory fetches to compute the answer.

---

<div class="post-metadata">

### Author: ![mcovalt](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mcovalt/32/1722_2.png) [@mcovalt](https://discourse.julialang.org/u/mcovalt)
#### Post date: [April 27, 2017, 11:13pm UTC](https://discourse.julialang.org/t/immutable-vs-type-reassignment-via-arrays-and-performance/3364/11 "2017-04-27T23:13:17Z")

</div>

Very helpful. Thanks all. To summarize and see if I got this right:

- Passing an `immutable(Float64)` to a function passes the actual `Float64`
- Passing a `mutable(Float64)` to a function passes a pointer pointing to the `Float64`
- Passing an `immutable(Array)` to a function passes a pointer pointing to the `Array`
- Passing a `mutable(Array)` to a function passes a pointer pointing to the pointer pointing to the `Array`
- Operations on arrays come with costly operations from the call to `getindex`, but these can be bypassed by disabling bounds checking via `@inbounds` or by typing the array with `Ref`: a type that originated for aiding C-interop.
- Although the performance cost of making an immutable field mutable via an array can be lessened by disabling bounds checking, data can be better organized in memory by wrapping mutable data in mutable types and immutable data in immutable types, therefore making the code more performant.

---

<div class="post-metadata">

### Author: ![fengyang.wang](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/fengyang.wang/32/104_2.png) [@fengyang.wang](https://discourse.julialang.org/u/fengyang.wang)
#### Post date: [April 28, 2017, 12:51am UTC](https://discourse.julialang.org/t/immutable-vs-type-reassignment-via-arrays-and-performance/3364/12 "2017-04-28T00:51:48Z")

</div>

I don’t understand what you mean by `mutable()` and `immutable()`, but it’s not correct in general to assume that immutable objects are unboxed (i.e. passed as raw bits). Generally, only `isbits` objects are passed to functions unboxed. The definition of `isbits` is roughly the same as the definition of POD in other languages.
