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.
- immutable types can be bits types:
julia> type MutFloat
julia> immutable ImmFloat
- Let’s compare the performance of these two types. Additionally, we’ll include an immutable type wrapping an array.
floats = rand(100000)
immfloats = map(ImmFloat, floats)
mutfloats = map(MutFloat, floats)
arrfloats = map(x -> ArrFloat([x]), floats)
println(@benchmark sum(t.x for t in immfloats))
println(@benchmark sum(t.x for t in mutfloats))
println(@benchmark sum(t.x for t in arrfloats))
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
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.
- 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:
immfloatints = map(ImmFloatInt, floats)
println(@benchmark sum(t.x for t in immfloatints))
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.)