Tasks and type stability


#1

I was experimenting with Julia’s coroutines to do some simple discrete event simulations.

I noticed that consume returns a type Any, as shown below. Performance could be much better if the returned type was known. Is there a way to do that?

function prtsk(tsk)
       for i=1:10
          u=consume(tsk)
          println(u)
       end
end
function yt()
       for i=1:10
           produce(i)
       end
end

ytt=Task(yt)

julia> prtsk(ytt)
1
2
3
4
5
6
7
8
9
10
julia> @code_warntype prtsk(ytt)
Variables:
  #self#::#prtsk
  tsk::Task
  #temp#::Int64
  i::Int64
  u::Any

Body:
  begin 
      SSAValue(2) = (Base.select_value)((Base.sle_int)(1,10)::Bool,10,(Base.box)(Int64,(Base.sub_int)(1,1)))::Int64
      #temp#::Int64 = 1
      3: 
      unless (Base.box)(Base.Bool,(Base.not_int)((#temp#::Int64 === (Base.box)(Int64,(Base.add_int)(SSAValue(2),1)))::Bool)) goto 15
      SSAValue(3) = #temp#::Int64
      SSAValue(4) = (Base.box)(Int64,(Base.add_int)(#temp#::Int64,1))
      i::Int64 = SSAValue(3)
      #temp#::Int64 = SSAValue(4) # line 3:
      u::Any = $(Expr(:invoke, LambdaInfo for consume(::Task), :(Main.consume), :(tsk))) # line 4:
      (Main.println)(u::Any)::Void
      13: 
      goto 3
      15: 
      return
  end::Void



#2

At a guess (untested), ytt is an object defined at global scope. Try putting it inside a function.


#3

ytt being a global doesn’t matter since it’s not used in the function. Type inference cannot see the control flow of coroutine so it can’t infer the return type of consume. You can add a type assert on it.


#4

After some digging, I learned that the produce/consume approach is deprecated in 0.6. The current documentation is quite sparse, however.

The help documentation makes it clear that a Channel type could be specified

I tried this new mechanism on 0.6.0-pre.alpha.231, but with little success


julia> function prtsk(c::Channel{Int})
                     for i=1:10
                        u=take!(c)
                        println(u)
                     end
              end
prtsk (generic function with 1 method)

julia> function yt(c::Channel)
              for i=1:10
                  put!(c,i)
              end
       end
yt (generic function with 1 method)

julia> chnl=Channel(yt,ctype=Int)
Channel{Int64}(sz_max:0,sz_curr:1)

julia> @code_warntype prtsk(chnl)
Variables:
  #self#::#prtsk
  c::Channel{Int64}
  i::Int64
  u::Any
  #temp#::Int64

Body:
  begin 
      SSAValue(2) = (Base.select_value)((Base.sle_int)(1, 10)::Bool, 10, (Base.sub_int)(1, 1)::Int64)::Int64
      #temp#::Int64 = 1
      3: 
      unless (Base.not_int)((#temp#::Int64 === (Base.add_int)(SSAValue(2), 1)::Int64)::Bool)::Bool goto 14
      SSAValue(3) = #temp#::Int64
      SSAValue(4) = (Base.add_int)(#temp#::Int64, 1)::Int64
      #temp#::Int64 = SSAValue(4) # line 3:
      u::Any = $(Expr(:invoke, MethodInstance for take!(::Channel{Int64}), :(Main.take!), :(c))) # line 4:
      (Main.println)(u::Any)::Void
      12: 
      goto 3
      14: 
      return
  end::Void

Not sure if this is by design, or would it warrant an issue.


#5

Interestingly, if I use a slight different mechanism for Channel generation, I get slight better generated code.


julia> function yt()
              c=Channel{Int}(10)
              @async begin   
              for i=1:10
                  put!(c,i)
              end
              close(c)
              end
              c
       end
yt (generic function with 2 methods)

julia> chnl=yt()
Channel{Int64}(sz_max:10,sz_curr:10)
julia> @code_warntype prtsk(chnl)
Variables:
  #self#::#prtsk
  c::Channel{Int64}
  i::Int64
  u::Int64
  #temp#::Int64

Body:
  begin 
      SSAValue(4) = (Base.select_value)((Base.sle_int)(1, 10)::Bool, 10, (Base.sub_int)(1, 1)::Int64)::Int64
      #temp#::Int64 = 1
      3: 
      unless (Base.not_int)((#temp#::Int64 === (Base.add_int)(SSAValue(4), 1)::Int64)::Bool)::Bool goto 20
      SSAValue(5) = #temp#::Int64
      SSAValue(6) = (Base.add_int)(#temp#::Int64, 1)::Int64
      #temp#::Int64 = SSAValue(6) # line 3:
      SSAValue(2) = $(Expr(:invoke, MethodInstance for take!(::Channel{Int64}), :(Main.take!), :(c)))
      u::Int64 = (Core.typeassert)((Base.convert)(Main.Int, SSAValue(2))::Any, Main.Int)::Int64 # line 4:
      $(Expr(:inbounds, false))
      # meta: location coreio.jl println 5
      SSAValue(3) = (Core.typeassert)(Base.STDOUT, Base.IO)::IO
      # meta: pop location
      $(Expr(:inbounds, :pop))
      (Base.print)(SSAValue(3), u::Int64, $(QuoteNode('\n')))::Void
      18: 
      goto 3
      20: 
      return
  end::Void

So in this version u is of type Int32 at the outset.


#6

I can’t reproduce your issue. The @code_warntype is free of type instabilities for me (except, of course, STDOUT).

Perhaps try updating to the latest beta?


#7

@fengyang.wang, I concur. In 0.6.0-pre.beta.146, I do not see any type instabilities.