Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

More aggressive allocation elimination. #16021

Merged
merged 1 commit into from
Apr 27, 2016
Merged

More aggressive allocation elimination. #16021

merged 1 commit into from
Apr 27, 2016

Conversation

yuyichao
Copy link
Contributor

  • Mutable allocations that aren't mutated or escaped

    I'm not sure why we were not doing this before since we have a very conservative escape check.
    Maybe there's some subtlety I'm not seeing?

    julia> type A{T}
               a::T
           end
    
    julia> f(x) = A(x).a
    f (generic function with 1 method)
    
    julia> @code_warntype f(1)
    Variables:
      #self#::#f
      x::Int64
    
    Body:
      begin  # REPL[2], line 1: # REPL[1], line 2: # REPL[1], line 2:
          (top(getfield))(Main,:A)::Type{A{T}}
          return x::Int64
      end::Int64

    (It might be more useful if this can happen before type inference.)

    julia> type B
               a
           end
    
    julia> g(x) = B(x).a
    g (generic function with 1 method)
    
    julia> @code_warntype g(1)
    Variables:
      #self#::#g
      x::Int64
    
    Body:
      begin  # REPL[5], line 1:
          return x::Int64
      end::Any
    
    julia> @code_llvm g(1)
    
    define %jl_value_t* @julia_g_54031(i64) #0 {
    top:
      %1 = call %jl_value_t* @jl_box_int64(i64 signext %0)
      ret %jl_value_t* %1
    }
  • Allocations that aren't used (Fix Omitting return value of inline function generate worse code. #12415)

    julia> f(a, b) = ((a, b); nothing)
    f (generic function with 2 methods)
    
    julia> @code_warntype f([], [])
    Variables:
      #self#::#f
      a::Array{Any,1}
      b::Array{Any,1}
    
    Body:
      begin  # REPL[8], line 1:
          a::Array{Any,1}
          b::Array{Any,1}
          return Main.nothing
      end::Void
    
    julia> @code_llvm f([], [])
    
    define void @julia_f_54044(%jl_value_t*, %jl_value_t*) #0 {
    top:
      ret void
    }

    This happens probably mainly due to inlining...

  • Allocations of fields that aren't used or escaped

    julia> function f(a, b, c)
               j = (a, (b, c))
               j[1] + j[2][1]
           end
    f (generic function with 1 method)
    
    julia> @code_warntype f(1, 2, "")
    Variables:
      #self#::#f
      a::Int64
      b::Int64
      c::ASCIIString
      j::Tuple{Int64,Tuple{Int64,ASCIIString}}
    
    Body:
      begin  # REPL[1], line 2:
          GenSym(0) = a::Int64
          GenSym(2) = b::Int64
          GenSym(3) = c::ASCIIString # REPL[1], line 3:
          return (Base.box)(Int64,(Base.add_int)(GenSym(0),GenSym(2)))
      end::Int64
    
    julia> @code_llvm f(1, 2, "")
    
    define i64 @julia_f_54032(i64, i64, %jl_value_t*) #0 {
    top:
      %3 = add i64 %1, %0
      ret i64 %3
    }

The optimization still can't catch all the obvious cases. The cases I found where it still fails to optimize are mainly due to not tracing variable assignments, e.g. (note the GenSym(1) = GenSym(0) should be trivially optimized out since both are ssa values),

julia> function f(a, b, c)
           x, (y, z) = (a, (b, c))
           x + y
       end
f (generic function with 1 method)

julia> @code_warntype f(1, 2, "")
Variables:
  #self#::#f
  a::Int64
  b::Int64
  c::ASCIIString
  x::Int64
  y::Int64
  z::ASCIIString
  #temp#::Int64

Body:
  begin  # REPL[1], line 2:
      GenSym(0) = (top(tuple))(b::Int64,c::ASCIIString)::Tuple{Int64,ASCIIString}
      GenSym(1) = GenSym(0)
      #temp#::Int64 = 1
      GenSym(4) = (Base.getfield)(GenSym(1),1)::Union{ASCIIString,Int64}
      GenSym(5) = (Base.box)(Int64,(Base.add_int)(1,1))
      y::Int64 = GenSym(4)
      #temp#::Int64 = GenSym(5)
      GenSym(6) = (Base.getfield)(GenSym(1),2)::Union{ASCIIString,Int64}
      GenSym(7) = (Base.box)(Int64,(Base.add_int)(2,1))
      z::ASCIIString = GenSym(6)
      #temp#::Int64 = GenSym(7)
      a::Int64
      GenSym(0) # REPL[1], line 3:
      return (Base.box)(Int64,(Base.add_int)(a::Int64,y::Int64))
  end::Int64

Or not handling dummy use of variable in the ast (Note the j::Tuple{Int64,Int64,ASCIIString} at the end disables the optimization)

julia> function f(a, b, c)
           j = (a, b, c)
           x, y, z = j
           x + y
       end
f (generic function with 1 method)

julia> @code_warntype f(1, 2, "")
Variables:
  #self#::#f
  a::Int64
  b::Int64
  c::ASCIIString
  j::Tuple{Int64,Int64,ASCIIString}
  x::Int64
  y::Int64
  z::ASCIIString
  #temp#::Int64

Body:
  begin  # REPL[1], line 2:
      j::Tuple{Int64,Int64,ASCIIString} = (top(tuple))(a::Int64,b::Int64,c::ASCIIString)::Tuple{Int64,Int64,ASCIIString} # REPL[1], line 3:
      #temp#::Int64 = 1
      GenSym(3) = (Base.getfield)(j::Tuple{Int64,Int64,ASCIIString},1)::Union{ASCIIString,Int64}
      GenSym(4) = (Base.box)(Int64,(Base.add_int)(1,1))
      x::Int64 = GenSym(3)
      #temp#::Int64 = GenSym(4)
      GenSym(5) = (Base.getfield)(j::Tuple{Int64,Int64,ASCIIString},2)::Union{ASCIIString,Int64}
      GenSym(6) = (Base.box)(Int64,(Base.add_int)(2,1))
      y::Int64 = GenSym(5)
      #temp#::Int64 = GenSym(6)
      GenSym(7) = (Base.getfield)(j::Tuple{Int64,Int64,ASCIIString},3)::Union{ASCIIString,Int64}
      GenSym(8) = (Base.box)(Int64,(Base.add_int)(3,1))
      z::ASCIIString = GenSym(7)
      #temp#::Int64 = GenSym(8)
      j::Tuple{Int64,Int64,ASCIIString} # REPL[1], line 4:
      return (Base.box)(Int64,(Base.add_int)(x::Int64,y::Int64))
  end::Int64

For the second one, is there a way to check if the use can be ignored in a way that doesn't trigger #6846 ?

@yuyichao yuyichao added the performance Must go faster label Apr 23, 2016
@yuyichao
Copy link
Contributor Author

The first and the second optimization combined breaks my favorite GC allocation fast path benchmark

julia> function f(n)
           for i in 1:n
               Ref(i)
           end
       end
f (generic function with 1 method)

julia> @code_llvm f(10)

define void @julia_f_53825(i64) #0 {
top:
  ret void
}

I'm not really missing it though ;-p

@yuyichao yuyichao force-pushed the yyc/alloc-elim branch 2 times, most recently from 7e8290e to 8d105b9 Compare April 25, 2016 02:50
@yuyichao
Copy link
Contributor Author

Any comment?

@JeffBezanson
Copy link
Member

LGTM.

is there a way to check if the use can be ignored

You can check the used-undef flag for the slot, which is 32 (I know; will add names for these!)

@yuyichao
Copy link
Contributor Author

yuyichao commented Apr 25, 2016

And while trying to make that change, I discovered a bug in the current implementation due to assuming linearty of control flow.......

julia> function f(a, b, c)
           @goto a
           @label b
           return j[1]
           @label a
           j = (a, b, c)
           @goto b
       end
f (generic function with 1 method)

julia> @code_warntype f(1, 1, "")
Variables:
  #self#::#f
  a::Int64
  b::Int64
  c::ASCIIString
  j::Tuple{Int64,Int64,ASCIIString}

Body:
  begin  # REPL[1], line 2:
      NewvarNode(:(j::Tuple{Int64,Int64,ASCIIString}))
      goto 9 # REPL[1], line 3:
      5:  # REPL[1], line 4:
      return (Base.getfield)(j::Tuple{Int64,Int64,ASCIIString},1)::Int64 # REPL[1], line 5:
      9:  # REPL[1], line 6:
      GenSym(0) = a::Int64
      GenSym(1) = b::Int64
      GenSym(2) = c::ASCIIString # REPL[1], line 7:
      goto 5
  end::Int64

julia> f(1, 1, "")

signal (4): 非法指令
while loading no file, in expression starting on line 0
f at ./REPL[1]:4
unknown function (ip: 0x7f44c4111053)
[inline] at /build/julia-git/src/julia-avx2/src/julia_internal.h:69
jl_call_method_internal at /build/julia-git/src/julia-avx2/src/gf.c:1430
do_call at /build/julia-git/src/julia-avx2/src/interpreter.c:58
eval at /build/julia-git/src/julia-avx2/src/interpreter.c:181
[inline] at /build/julia-git/src/julia-avx2/src/interpreter.c:25
jl_interpret_toplevel_expr at /build/julia-git/src/julia-avx2/src/toplevel.c:535
jl_toplevel_eval_in_warn at /build/julia-git/src/julia-avx2/src/builtins.c:545
eval at ./boot.jl:236
unknown function (ip: 0x7f46c66d8258)
[inline] at /build/julia-git/src/julia-avx2/src/julia_internal.h:69
jl_call_method_internal at /build/julia-git/src/julia-avx2/src/gf.c:1430
[inline] at ./REPL.jl:3
eval_user_input at ./REPL.jl:62
unknown function (ip: 0x7f44c43b5576)
[inline] at /build/julia-git/src/julia-avx2/src/julia_internal.h:69
jl_call_method_internal at /build/julia-git/src/julia-avx2/src/gf.c:1430
[inline] at ./REPL.jl:92
#1 at ./event.jl:46
unknown function (ip: 0x7f44c459258f)
[inline] at /build/julia-git/src/julia-avx2/src/julia_internal.h:69
jl_call_method_internal at /build/julia-git/src/julia-avx2/src/gf.c:1430
[inline] at /build/julia-git/src/julia-avx2/src/julia.h:1339
jl_apply at /build/julia-git/src/julia-avx2/src/task.c:249
unknown function (ip: 0xffffffffffffffff)
Allocations: 3419201 (Pool: 3417738; Big: 1463); GC: 6

(The error is illegal instruction....)

* Mutable allocations that aren't mutated or escaped
* Allocations that aren't used (Fix #12415)
* Allocations of fields that aren't used or escaped
@yuyichao
Copy link
Contributor Author

yuyichao commented Apr 26, 2016

The second case is handled now.

julia> function f(a, b, c)
           j = (a, b, c)
           x, y, z = j
           x + y
       end
f (generic function with 1 method)

julia> @code_warntype f(1, 2, "3")
Variables:
  #self#::#f
  a::Int64
  b::Int64
  c::ASCIIString
  j::Tuple{Int64,Int64,ASCIIString}
  x::Int64
  y::Int64
  z::ASCIIString
  #temp#::Int64

Body:
  begin  # REPL[1], line 2:
      GenSym(3) = a::Int64
      GenSym(4) = b::Int64
      GenSym(5) = c::ASCIIString # REPL[1], line 3:
      #temp#::Int64 = 1
      GenSym(6) = (Base.box)(Int64,(Base.add_int)(1,1))
      x::Int64 = GenSym(3)
      #temp#::Int64 = GenSym(6)
      GenSym(7) = (Base.box)(Int64,(Base.add_int)(2,1))
      y::Int64 = GenSym(4)
      #temp#::Int64 = GenSym(7)
      GenSym(8) = (Base.box)(Int64,(Base.add_int)(3,1))
      z::ASCIIString = GenSym(5)
      #temp#::Int64 = GenSym(8) # REPL[1], line 4:
      return (Base.box)(Int64,(Base.add_int)(x::Int64,y::Int64))
  end::Int64

The case that used to trap should be correctly handled by type inference now too

julia> function f(a, b)
           @goto a
           @label b
           return j[1]
           @label a
           j = (a, b)
           @goto b
       end
f (generic function with 1 method)

julia> @code_warntype f(1, 2)
Variables:
  #self#::#f
  a::Int64
  b::Int64
  j::Tuple{Int64,Int64}

Body:
  begin  # REPL[1], line 2:
      NewvarNode(:(j::Tuple{Int64,Int64}))
      goto 7
      4:  # REPL[1], line 4:
      return GenSym(0)
      7:  # REPL[1], line 6:
      GenSym(0) = a::Int64
      GenSym(1) = b::Int64 # REPL[1], line 7:
      goto 4
  end::Int64

However, codegen currently doesn't support this so it generates a undef return instead of a trap...

define i64 @julia_f_53618(i64, i64) #0 {
top:
  ret i64 undef
}

Not sure which one is better....

@yuyichao yuyichao merged commit 9e7a240 into master Apr 27, 2016
@yuyichao yuyichao deleted the yyc/alloc-elim branch April 27, 2016 01:12
@JeffBezanson
Copy link
Member

👍 Awesome. Could you add a test for the bug you found?

@yuyichao
Copy link
Contributor Author

The bug isn't completely fixed yet so I didn't add the test.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
performance Must go faster
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants