Yichao & Tom, Thank you both for your explanations. It's starting to come together for me now. Tom, I will admit that I'm unclear on what you mean by `f` not being accessed as `the same kind of function` between access in the global scope vs. access within the closure of `map`. I mean, I (more or less) understand the distinction you draw subsequently, and now I'm just trying to picture how that's working w/r/t to the Julia internals. As in, are the objects themselves of different types? Are they represented differently in memory? Or is it that they're treated differently by the compiler due to some subtlety about how `quote` expressions get lowered? Currently the most sensible mental picture of your explanation I can draw for myself involves the latter explanation (i.e. the compiler-related one).
In any case, I really appreciate your taking the time to reason through this with me. On Thursday, July 16, 2015 at 6:02:29 PM UTC-4, Tom Breloff wrote: > > David: the "f(src(i))" in the second call is referencing the local > argument "f::Function" passed into the map! method. It is not accessing > the same kind of function as if you defined it globally. I think the first > definition is effectively just grabbing f's Symbol, and then calling the > method associated with that symbol in global scope. When functions can be > resolved fully at compile-time, there's a chance for better type > resolution. The second version keeps a "Function" object around for every > call while the first version only uses that "Function" object to get the > symbol/name. > > Hope this helps? > > On Thu, Jul 16, 2015 at 4:21 PM, Yichao Yu <[email protected] <javascript:> > > wrote: > >> On Thu, Jul 16, 2015 at 4:15 PM, David Gold <[email protected] >> <javascript:>> wrote: >> > First, a note: Please disregard the use of `A` in the above function >> > definitions! Those ought to be `src`. I just got very confused as to why >> > those definitions worked at all, until I realized that my test `Array` >> > argument was also named `A`... So, the definitions in question ought to >> be >> > >> > function map!{F}(f::F, dest::AbstractArray, src::AbstractArray) >> > _f = Expr(:quote, f) >> > @eval begin >> > function func(dest, src) >> > for i in 1:length(dest) >> > dest[i] = $_f(src[i]) >> > end >> > end >> > func($dest, $src) >> > return $dest >> > end >> > end >> > >> > function map!{F}(f::F, dest::AbstractArray, src::AbstractArray) >> > >> > function func(dest, src) >> > >> > for i in 1:length(dest) >> > >> > dest[i] = f(src[i]) >> > >> > end >> > >> > end >> > >> > func(dest, src) >> > >> > return dest >> > >> > end >> > >> > >> > (though technically only the `A` as called in `func(dest, A)` in the old >> > definitions really mattered). >> > >> > >> > Tom, >> > >> > I don't understand the difference that global scope makes. `f` is not >> passed >> >> My guess is that this is because closures are slow in julia and IIRC >> type inference is not doing a very good job at infering referencing to >> variables in the outer scope, especially since those can be changed by >> the closure. >> >> > as an argument to `func` -- why is the subsequent call `func(dest, >> src)` not >> > amenable to type inference w/r/t to the runtime types of `dest`, `src` >> and >> > the knowledge that the particular value of `f` as passed to `map!` is >> > hardcoded into the `func`'s body? Does the compiler implicitly treat >> `f` as >> > an "argument" of `func` when it senses that it is inherited from the >> closure >> > defined by `map`? Does the fact that `eval` works in global scope >> > effectively "trick" (not at all confident in this word choice) the >> compiler >> > into forgetting that `f` is only present in the body of `func` because >> it >> > was at one point the argument of `map!`? >> > >> > On Thursday, July 16, 2015 at 1:16:12 PM UTC-4, Tom Breloff wrote: >> >> >> >> I believe eval puts the function in global scope and thus has complete >> >> type information on the function. Your second attempt takes in a >> "Function" >> >> type which could be anything, and thus the compiler can't specialize >> very >> >> much. This problem may eventually go away if the Function type can be >> >> parametized with input and output type information. >> >> >> >> On Thu, Jul 16, 2015 at 11:22 AM, David Gold <[email protected]> >> wrote: >> >>> >> >>> Suppose I want to apply the trick that makes `broadcast!` fast to >> `map!`. >> >>> Because of the specificity of `map!`'s functionality, I don't >> necessarily >> >>> need to cache the internally declared functions, so I just write: >> >>> >> >>> function map!{F}(f::F, dest::AbstractArray, src::AbstractArray) >> >>> _f = Expr(:quote, f) >> >>> @eval begin >> >>> function func(dest, A) >> >>> for i in 1:length(dest) >> >>> dest[i] = $_f(A[i]) >> >>> end >> >>> end >> >>> func($dest, $A) >> >>> return $dest >> >>> end >> >>> end >> >>> >> >>> which does indeed show improved performance: >> >>> >> >>> srand(1) >> >>> N = 5_000_000 >> >>> A = rand(N) >> >>> X = Array(Float64, N) >> >>> f(x) = 5 * x >> >>> map!(f, X, A); >> >>> >> >>> julia> map!(f, X, A); >> >>> >> >>> >> >>> julia> @time map!(f, X, A); >> >>> >> >>> 17.459 milliseconds (2143 allocations: 109 KB) >> >>> >> >>> >> >>> julia> Base.map!(f, X, A); >> >>> >> >>> >> >>> julia> @time Base.map!(f, X, A); >> >>> >> >>> 578.520 milliseconds (19999 k allocations: 305 MB, 6.45% gc time) >> >>> >> >>> >> >>> Okay. But the following attempt does not experience the same speedup: >> >>> >> >>> >> >>> function map!{F}(f::F, dest::AbstractArray, src::AbstractArray) >> >>> >> >>> function func(dest, A) >> >>> >> >>> for i in 1:length(dest) >> >>> >> >>> dest[i] = f(A[i]) >> >>> >> >>> end >> >>> >> >>> end >> >>> >> >>> func(dest, A) >> >>> >> >>> return dest >> >>> >> >>> end >> >>> >> >>> >> >>> julia> map!(f, X, A); >> >>> >> >>> >> >>> julia> @time map!(f, X, A); >> >>> >> >>> 564.823 milliseconds (20000 k allocations: 305 MB, 6.44% gc time) >> >>> >> >>> >> >>> My question is: Why is `eval`-ing the body of `map!` necessary for >> >>> supporting the type inference/other optimizations that give the first >> >>> revised `map!` method greater performance? I suspect that there's >> something >> >>> about what `eval` does, aside from just "evaluate an expression" that >> I'm >> >>> not quite grokking -- but what? Also, what risks in particular does >> invoking >> >>> `eval` at runtime inside the body of a function -- as opposed to >> directly >> >>> inside the global scope of a module -- pose? >> >>> >> >>> >> >>> Thanks, >> >>> >> >>> D >> >>> >> >>> >> >> >> > >> > >
