If you called f(x) which calls g(x), but g(x) does not exist, you are going
to get a no-method error. Or if you are using run-time eval to insert code
into the compile-time environment, you are forcing Julia to re-evaluate a
lot of assumptions anyways, so any performance benefits of assuming const
would be swamped by the additional cost imposed of having a changing idea
of what g(x) does. In short, if g(x) isn't defined by the time you call it,
hoping that the compiler noticed one of your arguments was const should be
the least of your concerns.

The memcpy function spec explicitly forbids aliasing – I don't understand
how that is relevant. I thought I heard that Fortran did forbid aliasing,
and could actually achieve some code optimization from this fact?

Preventing aliasing in general is impossible, if only looking at the
function arguments. For example, all of the following sometimes alias,
although there are cases for each that the compiler could not possibly
detect, at compile or runtime:

dict[1] = dict.keys

something(a) = (a[x] = 1)
something(x)

call_f(a, T(a))

@everywhere compute(g)

That is why I've considered adding a "frozen" or "const" flag to the object
instance itself, rather than to the variable binding. I have a suspicion
that this is the property that is actually desired. It might even be
possible to implement this for all types by stealing a bit from the type
tag field.


On Wed, Aug 20, 2014 at 11:36 PM, <[email protected]> wrote:

> Jameson,
>
> You wrote that the compiler can already tell whether or not a function
> modifies one of its mutable arguments.  Say that the function is f, the
> mutable argument is x, and that f contains a call like g(x), where g is
> another function. Then apparently in order to analyze f the compiler would
> have to know whether or not g modifies its argument.  But how can it tell,
> since in Julia the function g might not even have been parsed until that
> statement is encountered?
>
> With regard to your other point, I agree with you that aliasing is a
> significant loophole.  Although this is getting off topic, it seems to me
> that the Julia community should simply declare that aliasing between
> read/write function arguments (or write/write) is not allowed in Julia.
>  The C community did not have that luxury because of legacy tricks with the
> memcpy function, and neither did the Fortran community because of the
> legacy trick of equivalencing many smaller arrays on top of one big one.
> Since Julia gets to start with a clean slate, why not forbid aliasing?
>
> -- 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