Julia: Document type specialization

Created on 27 Aug 2017  路  10Comments  路  Source: JuliaLang/julia

On 0.6.0,

foo1(t::Type, x)            = Nullable{t}(sin(x))
foo2(t::Type{T}, x) where T = Nullable{t}(sin(x))

@btime foo1(Float64, 2)
  408.815 ns (2 allocations: 48 bytes)    # same with @allocated
@btime foo2(Float64, 2)
  17.359 ns (0 allocations: 0 bytes)

However, both functions are @inferred correctly, and have the same @code_llvm. Why does foo1 allocate?

Might be related to #19137

doc

Most helpful comment

Can anyone please explain to me how f1/foo1 can be type-stable, if the argument isn't being specialized on?

Type stable means that the inference can know what the return (or other variable) type is (are). Specialize means the compiler generate code for a specific input. The latter requires the former but is not implied by it. We do more inference than specialization to avoid excess code generation.

All 10 comments

Does that issue affect @allocated? It reports 48 bytes for foo1 and 0 for foo2.

I didn't see the @allocated comment but I think the problem is when another function calls foo e.g:

f1() = foo1(Float64, 2)
f2() = foo2(Float64, 2)

and look at generated code for f1and f2.

That makes sense. Can anyone please explain to me how f1/foo1 can be type-stable, if the argument isn't being specialized on? My guess is that it's type-stable because Julia runs inference to get the output type of foo1(Float64, 2) and foo1(Int, 2), even if it compiles only one "method specialization" for these two. That specialization's return value's type is Nullable(_), and that's why it allocates the result on the heap. Is that right?

Can anyone please explain to me how f1/foo1 can be type-stable, if the argument isn't being specialized on?

Type stable means that the inference can know what the return (or other variable) type is (are). Specialize means the compiler generate code for a specific input. The latter requires the former but is not implied by it. We do more inference than specialization to avoid excess code generation.

MyType{T} = Type{T}
MyType2 = Type

foo1(t::Type, x)    = Nullable{t}(sin(x))
foo3(t::MyType, x)  = Nullable{t}(sin(x))
foo4(t::MyType2, x) = Nullable{t}(sin(x))

@btime foo1(Float64, 2)
  408.815 ns (2 allocations: 48 bytes)
@btime foo3(Float64, 2)
  17.358 ns (0 allocations: 0 bytes)
@btime foo4(Float64, 2)
  349.832 ns (2 allocations: 48 bytes)

MyType == MyType2  # true

While I can see where these results are coming from, it's very subtle behavior, with a large performance impact.

Don't you need a const on the MyType2?

Same results.

Oh yeah, it's only used in the function definition.

Was this page helpful?
0 / 5 - 0 ratings