JuliaLang / julia

The Julia Programming Language
https://julialang.org/
MIT License
45.65k stars 5.48k forks source link

Can we support a way to make `@code_typed` stop lying to us all? :) #32834

Open NHDaly opened 5 years ago

NHDaly commented 5 years ago

@code_typed (and code_typed) will sometimes report a different result than actually gets inferred if you call the function. For example:

julia> f1(x) = Base.typemax(x)
f1 (generic function with 1 method)

julia> @code_typed f1(Int)
CodeInfo(
1 ─     return 9223372036854775807
) => Int64

Here @code_typed shows you fully specialized code, even though in reality this call will not be specialized (it will be a dynamic dispatch to typemax(Type{Int})).

xref: the documentation change here: https://github.com/JuliaLang/julia/pull/32817

Note that @code_typed and friends will always show you specialized code, even if Julia would not normally specialize that method call. You need to check the [method internals](@ref ast-lowered-method) if you want to see whether specializations are generated when argument types are changed.


Could we either change @code_typed or add a flag to @code_typed that will show you the code as julia would actually specialize it, instead of fully specialized to your argument type? :)

NHDaly commented 5 years ago

CC: @vtjnash (opening this issue based on our conversation)

iamed2 commented 5 years ago

There's this: https://github.com/JuliaLang/julia/issues/23749 which might be the same?

DilumAluthge commented 5 years ago

Could we either change @code_typed or add a flag to @code_typed that will show you the code as julia would actually specialize it, instead of fully specialized to your argument type? :)

I like the idea of adding a keyword argument that would let you choose!

DilumAluthge commented 5 years ago

Maybe a keyword argument called specialize? Possible values would be :full or :heuristic.

@code_typed specialize=:full f1(Int) would be the current behavior.

@code_typed specialize=:heuristic f1(Int) would actually use the specialization heuristic.

chethega commented 5 years ago

I propose to name the keywords specialize=:real vs specialize=:full. The jokes write themselves, with many compiler people caring about fully realized code, and most users caring about really existing code.

DilumAluthge commented 5 years ago

specialize=:full and specialize=:real seems fine to me!

Another options would be specialize=:full and specialize=:actual.

DilumAluthge commented 5 years ago

Interestingly enough, the behavior is different for keyword arguments vs positional arguments. As an example:

julia> f(x) = Base.typemax(x)
f (generic function with 1 method)

julia> g(; y) = Base.typemax(y)
g (generic function with 1 method)

julia> @code_typed f(Int)
CodeInfo(
1 ─     return 9223372036854775807
) => Int64

julia> @code_typed g(y = Int)
CodeInfo(
1 ── %1  = Base.getfield(@_2, :y)::DataType
│    %2  = Base.sle_int(1, 1)::Bool
└───       goto #3 if not %2
2 ── %4  = Base.sle_int(1, 0)::Bool
└───       goto #4
3 ──       nothing::Nothing
4 ┄─ %7  = φ (#2 => %4, #3 => false)::Bool
└───       goto #6 if not %7
5 ──       invoke Base.getindex(()::Tuple, 1::Int64)::Union{}
└───       $(Expr(:unreachable))::Union{}
6 ┄─       goto #7
7 ──       goto #8
8 ──       goto #9
9 ──       goto #10
10 ─       nothing::Nothing
│    %16 = invoke Main.:(var"#g#3")(%1::Type, _3::typeof(g))::Any
└───       return %16
) => Any

@NHDaly Any idea why @code_typed might be giving different results for keyword arguments vs positional arguments?

NHDaly commented 5 years ago

@NHDaly Any idea why @code_typed might be giving different results for keyword arguments vs positional arguments?

@DilumAluthge Yeah, i think I remember reading once that Julia doesn't specialize on keyword arguments at all, period. So the @code_typed for g(y=Int) is an accurate reflection of the code julia will produce. That seems to be the case here:

julia> @btime f(Int)
  0.035 ns (0 allocations: 0 bytes)
9223372036854775807

julia> @btime g(y=Int)
  44.038 ns (0 allocations: 0 bytes)
9223372036854775807
DilumAluthge commented 5 years ago

Are you sure? The following example makes it seem like Julia is specializing on keyword arguments.

julia> foo(; x) = x + x + x
foo (generic function with 1 method)

julia> @code_native foo(; x = 1)
    .section    __TEXT,__text,regular,pure_instructions
; ┌ @ REPL[1]:1 within `foo##kw'
; │┌ @ REPL[1]:1 within `#foo#3'
; ││┌ @ operators.jl:529 within `+' @ REPL[1]:1
    imulq   $3, (%rdi), %rax
; │└└
    retq
    nopw    %cs:(%rax,%rax)
; └

julia> @code_native foo(; x = 1.0)
    .section    __TEXT,__text,regular,pure_instructions
; ┌ @ REPL[1]:1 within `foo##kw'
; │┌ @ REPL[1]:1 within `#foo#3'
; ││┌ @ operators.jl:529 within `+' @ REPL[1]:1
    vmovsd  (%rdi), %xmm0           ## xmm0 = mem[0],zero
    vaddsd  %xmm0, %xmm0, %xmm1
    vaddsd  %xmm1, %xmm0, %xmm0
; │└└
    retq
    nopl    (%rax)
; └
KristofferC commented 5 years ago

Yeah, i think I remember reading once that Julia doesn't specialize on keyword arguments at all, period.

That is not true. With some will you can dig through things:

julia> f(x=1; y=2, z=3) = x + y + z
f (generic function with 2 methods)

julia> @code_warntype Core.kwfunc(f)((z=2,y=1,), f, 1)
Body::Int64
....
7 ┄ %21 = Main.:(var"#f#7")(y, z, @_3, x)::Int64
└──       return %21

julia> @code_warntype var"#f#7"(1, 2, f, 2)
Variables
  #f#7::Core.Compiler.Const(#f#7, false)
  y::Int64
  z::Int64
  @_4::Core.Compiler.Const(f, false)
  x::Int64

Body::Int64
1 ─ %1 = (x + y + z)::Int64
└──      return %1

julia> @code_warntype var"#f#7"(1, 2.0, f, 2)
Variables
  #f#7::Core.Compiler.Const(#f#7, false)
  y::Int64
  z::Float64
  @_4::Core.Compiler.Const(f, false)
  x::Int64

Body::Float64
1 ─ %1 = (x + y + z)::Float64
└──      return %1

So the innermost function (var"#f#7") which contains the body of the function does gets specialized

knuesel commented 3 years ago

Are @code_llvm and @code_native also "lying"?

julia> f1(x) = Base.typemax(x)
f1 (generic function with 1 method)

julia> @code_native f1(Int)
    .text
; ┌ @ REPL[2]:1 within `f1'
    movabsq $9223372036854775807, %rax # imm = 0x7FFFFFFFFFFFFFFF
    retq
    nopl    (%rax,%rax)
; └

Or has something changed and the example at the top is no longer relevant for this issue?

mbauman commented 3 years ago

Yes, that is the same "lie" that's demonstrated in the first post by @code_typed. All the code macros work the same way — they ask for the types of their arguments and ask for generated code assuming it'll get specialized for those types. But in this example, Julia will only do that in practice if you coerce it to (e.g., if f1 has a f1(::Type{T}) where T = ... definition).

See https://docs.julialang.org/en/v1/manual/performance-tips/#Be-aware-of-when-Julia-avoids-specializing for more details.

NHDaly commented 3 years ago

@YingboMa told me at JuliaCon that he's pretty sure that Cthulhu.jl is taking pains to make sure it's telling "the truth" about this. Is that right? (cc: @vchuravy?)

If so, can we port that code into Base for this? It would be really nice if this was resolved.

timholy commented 3 years ago

With Cthulhu it depends on how you get there; if you ask from top level you'll get the same result you do for the macros. But if you get to it via the normal calling path then I think it mostly gets the answer right. I may have still seen one exception so I don't want to claim it too strongly, but fundamentally now (and only on 1.7+) what Cthulhu does is re-use the inference work that normally gets done by the compiler.