Sorry I'm not being very clear. Lets try some code. I ran your first version, which creates a global method "func" which is a function specialized on the function f. Here's the comparison to the local function that is created in the second version. Notice the areas in bold, which are where the function is called.
# this is the "func" from the first version julia> println(@code_typed func(x,x)) Any[:($(Expr(:lambda, Any[:dest,:src], Any[Any[Any[:dest,Array{Float64,1},0],Any[:src,Array{Float64,1},0],Any[symbol("#s1"),Int64,2],Any[:i,Int64,18]],Any[],Any[UnitRange{Int64},Tuple{Int64,Int64},Float64,Int64,Float64,Float64,Float64,Int64,Int64],Any[]], :(begin # none, line 5: GenSym(3) = (top(arraylen))(dest::Array{Float64,1})::Int64 GenSym(0) = $(Expr(:new, UnitRange{Int64}, 1, :(((top(getfield))(Intrinsics,:select_value))((top(sle_int))(1,GenSym(3))::Bool,GenSym(3),(top(box))(Int64,(top(sub_int))(1,1)))::Int64))) #s1 = (top(getfield))(GenSym(0),:start)::Int64 unless (top(box))(Bool,(top(not_int))(#s1::Int64 === (top(box))(Int64,(top(add_int))((top(getfield))(GenSym(0),:stop)::Int64,1))::Bool)) goto 1 2: GenSym(7) = #s1::Int64 GenSym(8) = (top(box))(Int64,(top(add_int))(#s1::Int64,1)) i = GenSym(7) #s1 = GenSym(8) # line 6: *GenSym(4) = (top(arrayref))(src::Array{Float64,1},i::Int64)::Float64* * GenSym(6) = (top(ccall))((top(tuple))("sin",GlobalRef(Base.Math,:libm))::Tuple{ASCIIString,ASCIIString},Float64,(top(svec))(Float64)::SimpleVector,GenSym(4),0)::Float64* * GenSym(2) = (GlobalRef(Base.Math,:nan_dom_err))(GenSym(6),GenSym(4))::Float64* (top(arrayset))(dest::Array{Float64,1},GenSym(2),i::Int64)::Array{Float64,1} 3: unless (top(box))(Bool,(top(not_int))((top(box))(Bool,(top(not_int))(#s1::Int64 === (top(box))(Int64,(top(add_int))((top(getfield))(GenSym(0),:stop)::Int64,1))::Bool)))) goto 2 1: 0: return end::Void))))] #this is a slightly modified second version: julia> function map2!{F}(f::F, dest::AbstractArray, src::AbstractArray) function func(dest, src) for i in 1:length(dest) dest[i] = f(src[i]) end end println(@code_typed func(dest, src)) return dest end map2! (generic function with 1 method) julia> map2!(sin, x,x) Any[:($(Expr(:lambda, Any[:dest,:src], Any[Any[Any[:dest,Array{Float64,1},0],Any[:src,Array{Float64,1},0],Any[symbol("#s1"),Int64,2],Any[:i,Int64,18]],Any[Any[:f,Function,1]],Any[UnitRange{Int64},Tuple{Int64,Int64},Any,Int64,Int64,Int64],Any[:F]], :(begin # none, line 3: GenSym(3) = (top(arraylen))(dest::Array{Float64,1})::Int64 GenSym(0) = $(Expr(:new, UnitRange{Int64}, 1, :(((top(getfield))(Intrinsics,:select_value))((top(sle_int))(1,GenSym(3))::Bool,GenSym(3),(top(box))(Int64,(top(sub_int))(1,1)))::Int64))) #s1 = (top(getfield))(GenSym(0),:start)::Int64 unless (top(box))(Bool,(top(not_int))(#s1::Int64 === (top(box))(Int64,(top(add_int))((top(getfield))(GenSym(0),:stop)::Int64,1))::Bool)) goto 1 2: GenSym(4) = #s1::Int64 GenSym(5) = (top(box))(Int64,(top(add_int))(#s1::Int64,1)) i = GenSym(4) #s1 = GenSym(5) # line 4: *GenSym(2) = (f::F)((top(arrayref))(src::Array{Float64,1},i::Int64)::Float64)* (top(arrayset))(dest::Array{Float64,1},convert(Float64,GenSym(2)),i::Int64)::Array{Float64,1} 3: unless (top(box))(Bool,(top(not_int))((top(box))(Bool,(top(not_int))(#s1::Int64 === (top(box))(Int64,(top(add_int))((top(getfield))(GenSym(0),:stop)::Int64,1))::Bool)))) goto 2 1: 0: return end::Void))))] On Thursday, July 16, 2015 at 9:53:57 PM UTC-4, David Gold wrote: > > 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 <yyc...@gmail.com> wrote: >> >>> On Thu, Jul 16, 2015 at 4:15 PM, David Gold <david....@gmail.com> 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 <david....@gmail.com> >>> 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 >>> >>> >>> >>> >>> >> >>> > >>> >> >>