Stefan,

Thank you -- this is a very clear example that really hits the nail on the 
head.   Thanks for taking the time to explain it to me!  

So the only remaining unexplained matter is why 'slow' updating was so much 
slower than fast updating for my mutable example.  I thought about this 
more, and I guess the reason is that the routine type_upd_slow() must 
garbage-collect and reallocate each a[i] each time it is changed, whereas 
the other four routines (type_upd_none(), type_upd_fast(), 
immut_upd_none(), immut_upd_slow()) garbage collect a only once at the end 
of the main routine.  

So this means that, in the spirit of generic programming,  there is still 
one small hanging issue: is there a way to write a routine like type_upd or 
immut_upd that works well for an array a whether the entries of a are 
immutable or mutable.  (The issue is: 'fast updating' gives an error if the 
entries are immutable; 'slow updating' is extremely slow for the mutable 
case I suppose because of reallocation and garbage collection.)  However, 
this particular issue does not arise in my current project, so I wouldn't 
ask anyone to bother with it.

-- Steve



-- 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
>
>
>   
>

Reply via email to