# Different compilation results for global and "local" functions

**URL:** <https://discourse.julialang.org/t/different-compilation-results-for-global-and-local-functions/3802>\
**Category:** New to Julia\
**Created:** [May 19, 2017, 8:50am UTC](https://discourse.julialang.org/t/different-compilation-results-for-global-and-local-functions/3802 "2017-05-19T08:50:39Z")\
**Posts on this page:** 10\
**Page:** 1

<div class="post-metadata">

**Author:** ![wrgr](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/wrgr/32/2796_2.png) [@wrgr](https://discourse.julialang.org/u/wrgr)\
**Post date:** [May 19, 2017, 8:50am UTC](https://discourse.julialang.org/t/different-compilation-results-for-global-and-local-functions/3802/1 "2017-05-19T08:50:39Z")

</div>

I’ve tried to define essentially the same closure in two different ways (Julia 0.5, macOS):

```julia
function define()
    result = 0
    global function compute(k::Int)
        s = 2
        for i in 1:1000
            s += k
        end
        result = s
        return
    end
    function local_compute(k::Int)
        s = 2
        for i in 1:1000
            s += k
        end
        result = s
        return
    end
    local_compute
end

const compute2 = define()

```

Timing `compute(4)` yielded on average **15** _ns_, and timing `compute2(4)` yielded **40** _ns_, so the `global` function definition is at least 2 times faster. Doing `@code_llvm` also showed that the code which is generated for both methods are different. I checked the methods:

```julia
methods(compute2)
# (::#local_compute#4)(k::Int64)
methods(compute)
# compute(k::Int64)

```

And seemingly there is no type uncertainty or instability.  
Question: could someone explain the difference, and how to make the `local_compute` to compile and perform as well as its `global` counterpart? Thank you.

---

<div class="post-metadata">

**Author:** ![ScottPJones](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/scottpjones/32/146_2.png) [@ScottPJones](https://discourse.julialang.org/u/ScottPJones)\
**Post date:** [May 19, 2017, 9:44am UTC](https://discourse.julialang.org/t/different-compilation-results-for-global-and-local-functions/3802/2 "2017-05-19T09:44:44Z")

</div>

@yuyichao should comment, the difference between the two versions seems to be the overhead of calling `jl_get_ptls_states_fast`.

---

<div class="post-metadata">

**Author:** ![wrgr](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/wrgr/32/2796_2.png) [@wrgr](https://discourse.julialang.org/u/wrgr)\
**Post date:** [May 19, 2017, 4:12pm UTC](https://discourse.julialang.org/t/different-compilation-results-for-global-and-local-functions/3802/3 "2017-05-19T16:12:54Z")

</div>

agree, @yuyichao always is very helpful 🙂

---

<div class="post-metadata">

**Author:** ![musm](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/musm/32/3675_2.png) [@musm](https://discourse.julialang.org/u/musm)\
**Post date:** [May 19, 2017, 4:34pm UTC](https://discourse.julialang.org/t/different-compilation-results-for-global-and-local-functions/3802/4 "2017-05-19T16:34:16Z")

</div>

I think result needs to actually be a Ref  
i.e. `const result = Ref{Int}(0)`

then they are the same

```julia
julia> function define()
           const result = Ref{Int}(0)
           global function compute(k::Int)
               s = 2
               for i in 1:1000
                   s += k
               end
               result[] = s
               return
           end
           function local_compute(k::Int)
               s = 2
               for i in 1:1000
                   s += k
               end
               result[] = s
               return
           end
           local_compute
       end
define (generic function with 1 method)

julia> @btime lc(2)^C

julia> const compute2 = define()
(::local_compute) (generic function with 1 method)

julia> using BenchmarkTools

julia> @btime compute(20)
  3.947 ns (0 allocations: 0 bytes)

julia> @btime compute2(20)
  3.947 ns (0 allocations: 0 bytes)

```

---

<div class="post-metadata">

**Author:** ![yuyichao](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/yuyichao/32/20_2.png) [@yuyichao](https://discourse.julialang.org/u/yuyichao)\
**Post date:** [May 19, 2017, 5:08pm UTC](https://discourse.julialang.org/t/different-compilation-results-for-global-and-local-functions/3802/5 "2017-05-19T17:08:46Z")

</div>

No `ptls` overhead is much smaller. It’s closure lowering issue.

---

<div class="post-metadata">

**Author:** ![wrgr](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/wrgr/32/2796_2.png) [@wrgr](https://discourse.julialang.org/u/wrgr)\
**Post date:** [May 20, 2017, 10:34am UTC](https://discourse.julialang.org/t/different-compilation-results-for-global-and-local-functions/3802/6 "2017-05-20T10:34:22Z")

</div>

@musm, thank you for your suggestion. Julia did a heavy optimization in this case (that’s why you got identical execution times), but looking on LLVM, I again would say that the functions are not identical.  
The global one:

define void @julia\_compute\_71902(i64) #0 {  
top:  
%1 = mul i64 %0, 1000  
%2 = or i64 %1, 2  
store i64 %2, i64\* inttoptr (i64 4659960896 to i64\*), align 64  
ret void  
}

The “local” one:

define void @julia\_compute\_71958(%jl\_value\_t\*, i64) #0 {  
top:  
%2 = mul i64 %1, 1000  
%3 = or i64 %2, 2  
%4 = bitcast %jl\_value\_t\* %0 to i64\*\*  
%5 = load i64\*, i64\*\* %4, align 8  
store i64 %3, i64\* %5, align 16  
ret void  
}

As one can see, the local one gets additional argument `jl_value_t*`, which is used to `bitcast` the final result. Is this the “closure lowering issue” you mentioned, @yuyichao? If so, how to avoid it, how to make the second function do what the “normal”, `global` one, does?  
Also, I’m not an expert here, but why Julia adds this type information when I clearly specify the type of argument? It is as if the argument is still of type `Any`?

---

<div class="post-metadata">

**Author:** ![yuyichao](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/yuyichao/32/20_2.png) [@yuyichao](https://discourse.julialang.org/u/yuyichao)\
**Post date:** [May 20, 2017, 9:57pm UTC](https://discourse.julialang.org/t/different-compilation-results-for-global-and-local-functions/3802/7 "2017-05-20T21:57:56Z")

</div>

Do **NOT** use `code_llvm` as your first tool to identify performance issue. I really don’t know who started the tradition of using overkilling tools to confuse themselves (certainly not you) but this has cause too many people to look at low level representation and trying to guess what it means and confuse more people in that process (and I expect you to be one of the victims too).

In particular, do not use `code_llvm` if you have not used LLVM IR before and do not suggest it to anyone unless you can understand it yourself or at least identify the issue in a particular IR. It’s much better than `code_native` (which is almost never useful for performance issues) but we still have much better tools in most of the case.

In almost all cases, the right starting point is `code_warntype`. In this case,

```julia
julia> @code_warntype compute(1)
Variables:                                                                                          
  #self#::#compute                                                                                  
  k::Int64                                                                                          
  i::Int64                                                                                          
  #temp#::Int64                                                                                     
  s::Int64                                                                                          

Body:
  begin
      s::Int64 = 2 # line 5:
      SSAValue(3) = (Base.select_value)((Base.sle_int)(1, 1000)::Bool, 1000, (Base.sub_int)(1, 1)::Int64)::Int64
      #temp#::Int64 = 1
      5:
      unless (Base.not_int)((#temp#::Int64 === (Base.add_int)(SSAValue(3), 1)::Int64)::Bool)::Bool goto 14
      SSAValue(4) = #temp#::Int64
      SSAValue(5) = (Base.add_int)(#temp#::Int64, 1)::Int64
      #temp#::Int64 = SSAValue(5) # line 6:
      s::Int64 = (Base.add_int)(s::Int64, k::Int64)::Int64
      12:
      goto 5
      14: # line 8:
      SSAValue(2) = s::Int64
      (Core.setfield!)(Core.Box(0), :contents, SSAValue(2))::Int64 # line 9:
      return
  end::Void

julia> @code_warntype compute2(1)
Variables:
  #self#::#local_compute#1
  k::Int64
  i::Int64
  #temp#::Int64
  s::Int64

Body:
  begin
      s::Int64 = 2 # line 13:
      SSAValue(3) = (Base.select_value)((Base.sle_int)(1, 1000)::Bool, 1000, (Base.sub_int)(1, 1)::Int64)::Int64
      #temp#::Int64 = 1
      5:
      unless (Base.not_int)((#temp#::Int64 === (Base.add_int)(SSAValue(3), 1)::Int64)::Bool)::Bool goto 14
      SSAValue(4) = #temp#::Int64
      SSAValue(5) = (Base.add_int)(#temp#::Int64, 1)::Int64
      #temp#::Int64 = SSAValue(5) # line 14:
      s::Int64 = (Base.add_int)(s::Int64, k::Int64)::Int64
      12:
      goto 5
      14: # line 16:
      SSAValue(2) = s::Int64
      (Core.setfield!)((Core.getfield)(#self#::#local_compute#1, :result)::Any, :contents, SSAValue(2))::Int64 # line 17:
      return
  end::Void

```

I’ll not expect all users to identify this is related to closure lowering but it should be very clear what the difference is in the IR (the last expression) and the slower version also warns you about a `::Any` type.

---

<div class="post-metadata">

**Author:** ![yurivish](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/yurivish/32/307_2.png) [@yurivish](https://discourse.julialang.org/u/yurivish)\
**Post date:** [May 20, 2017, 10:42pm UTC](https://discourse.julialang.org/t/different-compilation-results-for-global-and-local-functions/3802/8 "2017-05-20T22:42:23Z")

</div>

Out of curiosity, is there enough information preserved in the transformation from surface syntax to the lowered + type-inferred AST to support the building of tools that show information from `code_warntype` in the context of the code as it was typed in by the user?

---

<div class="post-metadata">

**Author:** ![yuyichao](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/yuyichao/32/20_2.png) [@yuyichao](https://discourse.julialang.org/u/yuyichao)\
**Post date:** [May 20, 2017, 10:54pm UTC](https://discourse.julialang.org/t/different-compilation-results-for-global-and-local-functions/3802/9 "2017-05-20T22:54:34Z")

</div>

That’s what the `# line ...:` for, which is currently shown in a pretty confusing way. In the piece below the code between `# line 6:` and `# line 8:` are from a expression starting on line 6.

> [@yuyichao](#):
>
> ```julia
> #temp#::Int64 = SSAValue(5) # line 6:
> s::Int64 = (Base.add_int)(s::Int64, k::Int64)::Int64
> 12:
> goto 5
> 14: # line 8:
> 
> ```

---

<div class="post-metadata">

**Author:** ![wrgr](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/wrgr/32/2796_2.png) [@wrgr](https://discourse.julialang.org/u/wrgr)\
**Post date:** [May 21, 2017, 12:57pm UTC](https://discourse.julialang.org/t/different-compilation-results-for-global-and-local-functions/3802/10 "2017-05-21T12:57:00Z")

</div>

Thank you for `@code_warntype` suggestion, an awesome tool!
