Adam, I was the originator of this thread, and in the end I conceded that the current way that Julia handles immutable makes sense. Indeed, I authored a paragraph about the definition of immutable composite types for the 0.4.0 version of the Julia manual in which I put Stefan's explanation dated Aug 5 into my own words.
I think that adding ref arg and value arg types to Julia would make the type system too confusing. For example, what if you pass an Array of Arrays as a value arg? (Are the inner arrays also copied over?) Can you designate the elements of an Array to be ref/value? And what about multiple dispatch in the case that two methods have similar signatures except for a difference in ref and value args? On a related note, I have proposed that Julia should have a way to designate that a mutable arg to function is an inarg (i.e., not modifiable). My proposal is in a different thread in this newsgroup. The consensus seems to be that the proposal works fine with existing Julia, but some respondents questioned whether the proposal would get buy-in. -- Steve Vavasis 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 > > > >
