The truth lies somewhere between:
immutable TwoInts
a::Int
b::Int
end
function update1!(a)
@inbounds for i = 1:length(a)
a[i] = TwoInts(a[i].a,i+2)
end
a
end
function update2!(A)
@inbounds for i = 1:size(A,2)
A[2,i] = i+2
end
A
end
a = [TwoInts(i,i+1) for i = 1:10^7];
A = reinterpret(Int, a, (2, length(a)))
update1!(a)
update2!(A)
@time update1!(a)
@time update2!(A)
julia> include("/tmp/update.jl")
elapsed time: 0.038554672 seconds (13848 bytes allocated)
elapsed time: 0.389896915 seconds (0 bytes allocated)
elapsed time: 0.33763039 seconds (0 bytes allocated)
So there is some (small) advantage to mutability _if_ you're going to mutate
the values. What you're missing, Steve, is how immutables interact on a larger
scale. If the compiler knows that a value won't change, quite a few
optimizations are possible (http://en.wikipedia.org/wiki/Immutable_object).
Your example just doesn't happen to capture any of those advantages.
--Tim
On Tuesday, August 05, 2014 04:12:46 PM [email protected] wrote:
> Jason,
>
> You say that probably no improvement would be possible if 'fast' updating
> were available for immutable in my code. In this case, why is there such a
> huge difference between fast and slow updating for the non-immutable case?
> Is there some optimization available in the 'immutable' case but not the
> 'type' case, or is this a compiler bug?
>
> -- Steve
>
> On Tuesday, August 5, 2014 5:38:17 PM UTC-4, [email protected] wrote:
> > Dear Julia users,
> >
> > It seems to me that Julia's distinction between a 'type' and an
> > 'immutable' conflates two independent properties; the consequence of this
> > conflation is a needless loss of performance. In more detail, the
> > differences between a 'type' struct and 'immutable' struct in Julia are:
> >
> > 1. Assignment of 'type' struct copies a pointer; assignment of an
> > 'immutable' struct copies the data.
> >
> > 2. An array of type structs is an array of pointers, while an array of
> > immutables is an array of data.
> >
> > 3. Type structs are refcounted, whereas immutables are not. (This is not
> > documented; it is my conjecture.)
> >
> > 4. Fields in type structs can be modified, but fields in immutables
> > cannot.
> >
> > Clearly #1-#3 are related concepts. As far as I can see, #4 is completely
> > independent from #1-#3, and there is no obvious reason why it is forbidden
> > to modify fields in immutables. There is no analogous restriction in
> > C/C++.
> >
> > This conflation causes a performance hit. Consider:
> >
> > type floatbool
> >
> > a::Float64
> > b:Bool
> >
> > end
> >
> > If t is of type Array{floatbool,1} and I want to update the flag b in
> > t[10] to 'true', I say 't[10].b=true' (call this 'fast'update). But if
> > instead of 'type floatbool' I had said 'immutable floatbool', then to set
> > flag b in t[10] I need the more complex code t[10] =
> > floatbool(t[10].a,true) (call this 'slow' update).
> >
> > To document the performance hit, I wrote five functions below. The first
> > three use 'type' and either no update, fast update, or slow update; the
> > last two use 'immutable' and either no update or slow update. You can
> > see
> > a HUGE hit on performance between slow and fast update for `type'; for
> > immutable there would presumably also be a difference, although apparently
> > smaller. (Obviously, I can't test fast update for immutable; this is the
> > point of my message!)
> >
> > So why does Julia impose this apparently needless restriction on
> > immutable?
> >
> > -- Steve Vavasis
> >
> >
> > julia> @time testimmut.type_upd_none()
> > @time testimmut.type_upd_none()
> > elapsed time: 0.141462422 seconds (48445152 bytes allocated)
> >
> > julia> @time testimmut.type_upd_fast()
> > @time testimmut.type_upd_fast()
> > elapsed time: 0.618769232 seconds (48247072 bytes allocated)
> >
> > julia> @time testimmut.type_upd_slow()
> > @time testimmut.type_upd_slow()
> > elapsed time: 4.511306586 seconds (4048268640 bytes allocated)
> >
> > julia> @time testimmut.immut_upd_none()
> > @time testimmut.immut_upd_none()
> > elapsed time: 0.04480173 seconds (32229468 bytes allocated)
> >
> > julia> @time testimmut.immut_upd_slow()
> > @time testimmut.immut_upd_slow()
> > elapsed time: 0.351634871 seconds (32000096 bytes allocated)
> >
> > module testimmut
> >
> > type xytype
> >
> > x::Int
> > y::Float64
> > z::Float64
> > summed::Bool
> >
> > end
> >
> > immutable xyimmut
> >
> > x::Int
> > y::Float64
> > z::Float64
> > summed::Bool
> >
> > end
> >
> > myfun(x) = x * (x + 1) * (x + 2)
> >
> > function type_upd_none()
> >
> > n = 1000000
> > a = Array(xytype, n)
> > for i = 1 : n
> >
> > a[i] = xytype(div(i,2), 0.0, 0.0, false)
> >
> > end
> > numtri = 100
> > for tri = 1 : numtri
> >
> > sum = 0
> > for i = 1 : n
> >
> > @inbounds x = a[i].x
> > sum += myfun(x)
> >
> > end
> >
> > end
> >
> > end
> >
> >
> > function type_upd_fast()
> >
> > n = 1000000
> > a = Array(xytype, n)
> > for i = 1 : n
> >
> > a[i] = xytype(div(i,2), 0.0, 0.0, false)
> >
> > end
> > numtri = 100
> > for tri = 1 : numtri
> >
> > sum = 0
> > for i = 1 : n
> >
> > @inbounds x = a[i].x
> > sum += myfun(x)
> > @inbounds a[i].summed = true
> >
> > end
> >
> > end
> >
> > end
> >
> > function type_upd_slow()
> >
> > n = 1000000
> > a = Array(xytype, n)
> > for i = 1 : n
> >
> > a[i] = xytype(div(i,2), 0.0, 0.0, false)
> >
> > end
> > numtri = 100
> > for tri = 1 : numtri
> >
> > sum = 0
> > for i = 1 : n
> >
> > @inbounds x = a[i].x
> > sum += myfun(x)
> > @inbounds a[i] = xytype(a[i].x, a[i].y, a[i].z, true)
> >
> > end
> >
> > end
> >
> > end
> >
> >
> > function immut_upd_none()
> >
> > n = 1000000
> > a = Array(xyimmut, n)
> > for i = 1 : n
> >
> > a[i] = xyimmut(div(i,2), 0.0, 0.0, false)
> >
> > end
> > numtri = 100
> > for tri = 1 : numtri
> >
> > sum = 0
> > for i = 1 : n
> >
> > @inbounds x = a[i].x
> > sum += myfun(x)
> >
> > end
> >
> > end
> >
> > end
> >
> > function immut_upd_slow()
> >
> > n = 1000000
> > a = Array(xyimmut, n)
> > for i = 1 : n
> >
> > a[i] = xyimmut(div(i,2), 0.0, 0.0, false)
> >
> > end
> > numtri = 100
> > for tri = 1 : numtri
> >
> > sum = 0
> > for i = 1 : n
> >
> > @inbounds x = a[i].x
> > sum += myfun(x)
> > @inbounds a[i] = xyimmut(a[i].x, a[i].y, a[i].z, true)
> >
> > end
> >
> > end
> >
> > end
> >
> > end