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

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

Open
NHDaly opened this issue Aug 8, 2019 · 14 comments
Open

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

NHDaly opened this issue Aug 8, 2019 · 14 comments

Comments

@NHDaly
Copy link
Member

NHDaly commented Aug 8, 2019

@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(
1return 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: #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
Copy link
Member Author

NHDaly commented Aug 8, 2019

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

@iamed2
Copy link
Contributor

iamed2 commented Aug 8, 2019

There's this: #23749
which might be the same?

@DilumAluthge
Copy link
Member

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
Copy link
Member

DilumAluthge commented Aug 11, 2019

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
Copy link
Contributor

chethega commented Aug 11, 2019

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
Copy link
Member

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

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

@DilumAluthge
Copy link
Member

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(
1return 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
10nothing::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
Copy link
Member Author

NHDaly commented Oct 7, 2019

@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
Copy link
Member

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
Copy link
Sponsor Member

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
Copy link
Member

knuesel commented Dec 7, 2020

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
Copy link
Sponsor Member

mbauman commented Dec 7, 2020

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
Copy link
Member Author

NHDaly commented Aug 5, 2021

@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
Copy link
Sponsor Member

timholy commented Aug 5, 2021

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.

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

No branches or pull requests

9 participants