On Friday, January 22, 2016 12:03:02 PM Cedric St-Jean wrote:
> It looks like my understanding of FastAnonymous was flawed. Why doesn't it
> create the type at macro time, and just instantiate it at runtime, yielding
> 1 type / @anon ? Is there any complication that prevents that?
Yes:
for z in (1, 1.0)
f = @anon c->c+z
@show fieldtype(typeof(f), :z)
end
yields this output:
fieldtype(typeof(f),:z) = Int64
fieldtype(typeof(f),:z) = Float64
The fields of the constructed "function" need concrete type, if you're going to
get good performance.
Best,
--Tim
>
> Cédric
>
> On Friday, January 22, 2016 at 2:15:20 PM UTC-5, Tim Holy wrote:
> > On Friday, January 22, 2016 10:21:31 AM Bryan Rivera wrote:
> > > For 1000 elements:
> > >
> > > 0.00019s vs 0.035s respectively
> > >
> > > Thanks!
> >
> > Glad it helped.
> >
> > > Is the reason the faster code has more allocations bc it is
> > > inserting vars into the single function? (Opposed to the slower
> > > code already having its vars filled in.)
> >
> > Every time you call @anon, it creates a brand-new type (and an instance of
> > that type) that julia has never seen before. That requires JITting any
> > code
> > that gets invoked on this instance. So the usual advice, "run once to JIT,
> > then do your timing" doesn't work in this case: it JITs every time.
> >
> > --Tim
> >
> > > On Friday, January 22, 2016 at 12:23:59 PM UTC-5, Tim Holy wrote:
> > > > Just use
> > > >
> > > > z = 1
> > > > function2 = @anon c -> c + z
> > > > for z = 1:100
> > > >
> > > > function2.z = z
> > > > # do whatever with function2, including making a copy
> > > >
> > > > end
> > > >
> > > > --Tim
> > > >
> > > > On Friday, January 22, 2016 08:55:25 AM Cedric St-Jean wrote:
> > > > > (non-mutating) Closures and FastAnonymous work essentially the same
> >
> > way.
> >
> > > > > They store the data that is closed over (more or less) and a
> >
> > function
> >
> > > > > pointer. The thing is that there's only one data structure in Julia
> >
> > for
> >
> > > > all
> > > >
> > > > > regular anonymous functions, whereas FastAnonymous creates one per
> >
> > @anon
> >
> > > > > site. Because the FastAnonymous-created datatype is specific to that
> > > > > function definition, the standard Julia machinery takes over and
> > > >
> > > > produces
> > > >
> > > > > efficient code. It's just as good as if the function had been
> >
> > defined
> >
> > > > > normally with `function foo(...) ... end`
> > > > >
> > > > >
> > > > > for z = 1:100
> > > > >
> > > > > function2 = @anon c -> (c + z)
> > > > >
> > > > > dict[z] = function2
> > > > >
> > > > > end
> > > > >
> > > > >
> > > > > So we end up creating multiple functions for each z value.
> > > > >
> > > > >
> > > > > In this code, whether you use @anon or not, Julia will create 100
> >
> > object
> >
> > > > > instances to store the z values.
> > > > >
> > > > > The speed difference between the two will soon be gone.
> > > > > <https://github.com/JuliaLang/julia/pull/13412>
> > > > >
> > > > > Cédric
> > > > >
> > > > > On Friday, January 22, 2016 at 11:31:36 AM UTC-5, Bryan Rivera
> >
> > wrote:
> > > > > > I have to do some investigating here. I thought we could do
> >
> > something
> >
> > > > > > like that but wasn't quite sure how it would look.
> > > > > >
> > > > > > Check this out:
> > > > > >
> > > > > > This code using FastAnonymous optimizes to the very same code
> >
> > below it
> >
> > > > > > where functions have been manually injected:
> > > > > >
> > > > > > using FastAnonymous
> > > > > >
> > > > > >
> > > > > > function function1(a, b, function2)
> > > > > >
> > > > > > if(a > b)
> > > > > >
> > > > > > c = a + b
> > > > > > return function2(c)
> > > > > >
> > > > > > else
> > > > > >
> > > > > > # do anything
> > > > > > # but return nothing
> > > > > >
> > > > > > end
> > > > > >
> > > > > > end
> > > > > >
> > > > > >
> > > > > > z = 10
> > > > > > function2 = @anon c -> (c + z)
> > > > > >
> > > > > >
> > > > > > a = 1
> > > > > > b = 2
> > > > > > @code_llvm function1(a, b, function2)
> > > > > > @code_native function1(a, b, function2)
> > > > > >
> > > > > > Manually unrolled equivalent:
> > > > > >
> > > > > > function function1(a, b, z)
> > > > > >
> > > > > > if(a > b)
> > > > > >
> > > > > > c = a + b
> > > > > > return function2(c, z)
> > > > > >
> > > > > > else
> > > > > >
> > > > > > # do anything
> > > > > > # but return nothing
> > > > > >
> > > > > > end
> > > > > >
> > > > > > end
> > > > > >
> > > > > >
> > > > > > function function2(c, z)
> > > > > >
> > > > > > return c + z
> > > > > >
> > > > > > end
> > > > > >
> > > > > >
> > > > > > a = 1
> > > > > > b = 2
> > > > > > z = 10
> > > > > >
> > > > > >
> > > > > > @code_llvm function1(a, b, z)
> > > > > >
> > > > > > @code_native function1(a, b, z)
> > > > > >
> > > > > > However, this is a bit too simplistic. My program actually does
> >
> > this:
> > > > > > # Test to see if multiple functions are created. They are.
> > > > > > # We would only need to create a single function if we used julia
> > > >
> > > > anon,
> > > >
> > > > > > but its time inefficient.
> > > > > >
> > > > > > dict = Dict{Int, Any}()
> > > > > > for z = 1:100
> > > > > >
> > > > > > function2 = @anon c -> (c + z)
> > > > > >
> > > > > > dict[z] = function2
> > > > > >
> > > > > > end
> > > > > >
> > > > > >
> > > > > > a = 1
> > > > > > b = 2
> > > > > >
> > > > > > function test()
> > > > > >
> > > > > > function1(a,b, dict[100])
> > > > > > function1(a,b, dict[50])
> > > > > >
> > > > > > end
> > > > > >
> > > > > > @code_llvm test()
> > > > > > @code_native test()
> > > > > >
> > > > > >
> > > > > >
> > > > > > So we end up creating multiple functions for each z value. We
> >
> > could
> >
> > > > use
> > > >
> > > > > > Julia's anon funs, which would only create a single function,
> >
> > however
> >
> > > > > > these
> > > > > > lamdas are less performant than FastAnon.
> > > > > >
> > > > > > So its a space vs time tradeoff, I want the speed of FastAnon,
> >
> > without
> >
> > > > the
> > > >
> > > > > > spacial overhead of storing multiple functions.
> > > > > >
> > > > > > Can we be greedy? :)
> > > > > >
> > > > > > On Thursday, January 21, 2016 at 9:56:51 PM UTC-5, Cedric St-Jean
> > > >
> > > > wrote:
> > > > > >> Something like this?
> > > > > >>
> > > > > >> function function1(a, b, f) # Variable needed in callback fun
> > > >
> > > > injected.
> > > >
> > > > > >> if(a > b)
> > > > > >>
> > > > > >> c = a + b
> > > > > >> res = f(c) # Callback function has been injected.
> > > > > >> return res + 1
> > > > > >>
> > > > > >> else
> > > > > >>
> > > > > >> # do anything
> > > > > >> # but return nothing
> > > > > >>
> > > > > >> end
> > > > > >>
> > > > > >> end
> > > > > >>
> > > > > >> type SomeCallBack
> > > > > >>
> > > > > >> z::Int
> > > > > >>
> > > > > >> end
> > > > > >> Base.call(callback::SomeCallBack, c) = c + callback.z
> > > > > >>
> > > > > >> function1(2, 1, SomeCallBack(10))
> > > > > >>
> > > > > >> Because of JIT, this is 100% equivalent to your "callback
> >
> > function
> >
> > > > has
> > > >
> > > > > >> been injected" example, performance-wise. My feeling is that
> >
> > .call
> >
> > > > > >> overloading is not to be abused in Julia, so I would favor using
> >
> > a
> >
> > > > > >> regular
> > > > > >> function call with a descriptive name instead of call
> >
> > overloading,
> >
> > > > but
> > > >
> > > > > >> the
> > > > > >> same performance guarantees apply. Does that answer your
> >
> > question?
> >
> > > > > >> On Thursday, January 21, 2016 at 9:02:50 PM UTC-5, Bryan Rivera
> > > >
> > > > wrote:
> > > > > >>> I think what I wrote above might be too complicated, as it is an
> > > >
> > > > attempt
> > > >
> > > > > >>> to solve this problem.
> > > > > >>>
> > > > > >>> In essence this is what I want:
> > > > > >>>
> > > > > >>>
> > > > > >>> # wasmerged, _, _, _ = elide_pairwise!(ttree1, ttree2, canmerge;
> > > >
> > > > nbrs=idbgv)
> > > >
> > > > > >>> function function1(a, b, onGreaterThanCallback)
> > > > > >>>
> > > > > >>> if(a > b)
> > > > > >>>
> > > > > >>> c = a + b
> > > > > >>> res = onGreaterThanCallback(c, z)
> > > > > >>> return res + 1
> > > > > >>>
> > > > > >>> else
> > > > > >>>
> > > > > >>> # do anything
> > > > > >>> # but return nothing
> > > > > >>>
> > > > > >>> end
> > > > > >>>
> > > > > >>> end
> > > > > >>>
> > > > > >>>
> > > > > >>> global onGreaterThanCallback = (c) -> c + z
> > > > > >>>
> > > > > >>> function1(a, b, onGreaterThanCallback)
> > > > > >>>
> > > > > >>>
> > > > > >>> Problems:
> > > > > >>>
> > > > > >>> The global variable.
> > > > > >>>
> > > > > >>> The anonymous function which has performance impact (vs other
> > > > > >>> approaches). We could use Tim Holy's @anon, but then the value
> >
> > of
> >
> > > > `z`
> > > >
> > > > > >>> is
> > > > > >>> fixed at function definition, which we don't always want.
> > > > > >>>
> > > > > >>> I think that the ideal optimization would look like this:
> > > > > >>> function function1(a, b, z) # Variable needed in callback
> >
> > fun
> >
> > > > > >>> injected.
> > > > > >>>
> > > > > >>> if(a > b)
> > > > > >>>
> > > > > >>> c = a + b
> > > > > >>> res = c + z # Callback function has been injected.
> > > > > >>> return res + 1
> > > > > >>>
> > > > > >>> else
> > > > > >>>
> > > > > >>> # do anything
> > > > > >>> # but return nothing
> > > > > >>>
> > > > > >>> end
> > > > > >>>
> > > > > >>> end
> > > > > >>>
> > > > > >>>
> > > > > >>> function1(a, b, z)
> > > > > >>>
> > > > > >>> In OO languages we would be using an abstract class or its
> > > >
> > > > equivalent.
> > > >
> > > > > >>> But I've thought about it, and read the discussions on
> >
> > interfaces,
> >
> > > > and
> > > >
> > > > > >>> don't see those solutions optimizing the code out like I did
> >
> > above.
> >
> > > > > >>> Any ideas?