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

Add a statement-cost printer for analyzing inlining decisions #37275

Merged
merged 1 commit into from
Sep 7, 2020

Conversation

timholy
Copy link
Sponsor Member

@timholy timholy commented Aug 29, 2020

While it now has a doctest, https://docs.julialang.org/en/latest/devdocs/inference/#The-inlining-algorithm-(inline_worthy)-1 has bitrotted more than once. And it's sufficiently laborious that it discourages rapid investigation. This makes printing the costs easy.

Here's a demo:

julia> Base.print_statement_costs(map, (typeof(sqrt), Tuple{Int}))
map(f, t::Tuple{Any}) in Base at tuple.jl:169
  0 1%1  = Base.getfield(_3, 1, true)::Int64
  1%2  = Base.sitofp(Float64, %1)::Float64
  2%3  = Base.lt_float(%2, 0.0)::Bool
  0 └──       goto #3 if not %3
  0 2 ─       Base.Math.throw_complex_domainerror(:sqrt, %2)::Union{}
  0 └──       unreachable
 20 3%7  = Base.Math.sqrt_llvm(%2)::Float64
  0 └──       goto #4
  0 4 ─       goto #5
  0 5%10 = Core.tuple(%7)::Tuple{Float64}
  0 └──       return %10

The numbers in the first column are the inliner's assigned cost.

If folks like this, I can add a test to make sure this stays working. (done)

base/compiler/optimize.jl Outdated Show resolved Hide resolved
Print type-inferred and optimized code for `f` given argument types `types`,
prepending each line with its cost as estimated by the compiler's inlining engine.
"""
function print_statement_costs(io::IO, @nospecialize(f), @nospecialize(t); kwargs...)
Copy link
Sponsor Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe it makes sense to add a @code_ version of this to InteractiveUtils. Could always be done later of course.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No way, is that @code_costs?:joy:

Copy link
Sponsor Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or an argument to one of the others (cost = true)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI, I'm providing @code_costs and it's exactly @timholy who gave me the motivation to do so. 😄

I have some objections about the display style, but I agree that Julia supports that feature.

Copy link
Sponsor Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, nice!

Copy link
Sponsor Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like your printing quite a lot better. But if you do it as separate code-display (print(io, src); lines = readlines(io)) then one risks having the two get misaligned, whereas this version ensures that the two will always stay aligned.

Demo:

julia> function buildexpr()
           items = [7, 3]
           ex = quote
               X = $items
               for x in X
                   println(x)
               end
           end
           return ex
       end
buildexpr (generic function with 1 method)

julia> @code_typed buildexpr()
CodeInfo(
1%1  = Core.tuple(7, 3)::Core.Const((7, 3), false)
│   %2  = $(Expr(:foreigncall, :(:jl_alloc_array_1d), Vector{Int64}, svec(Any, Int64), 0, :(:ccall), Vector{Int64}, 2, 2))::Vector{Int64}
│         Base.arraysize(%2, 1)::Int64
└──       goto #7 if not true
2%5  = φ (#1 => 1, #6 => %10)::Int64%6  = φ (#1 => 1, #6 => %16)::Int64%7  = φ (#1 => 1, #6 => %17)::Int64%8  = Base.getfield(%1, %6, true)::Int64
│         Base.arrayset(false, %2, %8, %5)::Vector{Int64}%10 = Base.add_int(%5, 1)::Int64%11 = (%7 === 2)::Bool
└──       goto #4 if not %11
3 ─       goto #5
4%14 = Base.add_int(%7, 1)::Int64
└──       goto #5
5%16 = φ (#4 => %14)::Int64%17 = φ (#4 => %14)::Int64%18 = φ (#3 => true, #4 => false)::Bool%19 = Base.not_int(%18)::Bool
└──       goto #7 if not %19
6 ─       goto #2
7 ┄       goto #8
8%23 = Core._expr(:(=), :X, %2)::Expr%24 = $(Expr(:copyast, :($(QuoteNode(:(for x = X
      #= REPL[4]:6 =#
      println(x)
  end))))))::Expr%25 = Core._expr(:block, $(QuoteNode(:(#= REPL[4]:4 =#))), %23, $(QuoteNode(:(#= REPL[4]:5 =#))), %24)::Expr
└──       return %25
) => Expr

You can see statement %24 took more than one line to print.

Copy link
Contributor

@kimikage kimikage Aug 31, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just made a change that will make it easier to fix that problem. Perhaps that will be fixed in a few hours. 😃
Edit: Done. CodeCosts.jl v0.2.0 is available.

Copy link
Sponsor Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I switched the order of the printing so the cost comes first...I just needed to look a little harder at how these functions were working. I edited the top post to show the current output.

This comment was marked as resolved.

@timholy timholy removed the needs tests Unit tests are required for this change label Aug 31, 2020
@kimikage
Copy link
Contributor

kimikage commented Aug 31, 2020

  0 2 ─       Base.Math.throw_complex_domainerror(:sqrt, %2)::Union{}
  0 └──       unreachable

BTW, is this some kind of magic?

@timholy
Copy link
Sponsor Member Author

timholy commented Sep 1, 2020

#35982 and #30222 (the latter should be closed once I split out a couple of nuggets into a separate PR)

@kimikage
Copy link
Contributor

kimikage commented Sep 1, 2020

What I'm suggesting is the cost "zero". 😅

Edit:
Sorry. WRT the former I was aware of the one in May (returning a flat cost, not zero), so I had not understood the relationship with the latter.

@timholy
Copy link
Sponsor Member Author

timholy commented Sep 1, 2020

I'm not sure I understand your point, but the zero for the throw comes from

extyp = line == -1 ? Any : src.ssavaluetypes[line]
if extyp === Union{}
return 0

The old demo at https://docs.julialang.org/en/v1.6-dev/devdocs/inference/ set line to -1 (because the simple approach using map doesn't allow it), so it didn't return 0.

@timholy timholy merged commit a108d6c into master Sep 7, 2020
@timholy timholy deleted the teh/cost_printer branch September 7, 2020 08:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants