Open NHDaly opened 5 years ago
CC: @vtjnash (opening this issue based on our conversation)
There's this: https://github.com/JuliaLang/julia/issues/23749 which might be the same?
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!
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.
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.
specialize=:full
and specialize=:real
seems fine to me!
Another options would be specialize=:full
and specialize=:actual
.
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 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
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)
; └
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
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?
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.
@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.
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.
@code_typed
(andcode_typed
) will sometimes report a different result than actually gets inferred if you call the function. For example:Here
@code_typed
shows you fully specialized code, even though in reality this call will not be specialized (it will be a dynamic dispatch totypemax(Type{Int})
).xref: the documentation change here: https://github.com/JuliaLang/julia/pull/32817
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? :)