Invalidations findings (from a GMT case)

I’m left with only one for this workload but it’s not a mt_backedges. it a simple backedges and ascend errors with it

The code run

using SnoopCompileCore
invs = @snoopr using GMT, FFTW
tinf = @snoopi_deep GMT.plot(rand(5,2));
using SnoopCompile
trees = invalidation_trees(invs);
taletrees = precompile_blockers(trees, tinf)

results (in my master version)

1-element Vector{SnoopCompile.StaleTree}:
 inserting eltype(::Type{ChainRulesCore.ZeroTangent}) @ ChainRulesCore C:\Users\joaqu\.julia\packages\ChainRulesCore\C73ay\src\tangent_types\abstract_zero.jl:55 invalidated:
   backedges: 1: MethodInstance for eltype(::Type) at depth 0 with 26 children blocked 1.4864543999999997 inclusive time for 2 nodes


julia> tree = taletrees[1];

julia> sig, roots = tree.backedges[1];

julia> ascend(roots)
ERROR: MethodError: no method matching method(::Vector{SnoopCompileCore.InferenceTimingNode})

Closest candidates are:
  method(::Core.MethodInstance)
   @ Cthulhu C:\Users\joaqu\.julia\packages\Cthulhu\eS0ye\src\backedges.jl:84
  method(::Vector{Base.StackTraces.StackFrame})
   @ Cthulhu C:\Users\joaqu\.julia\packages\Cthulhu\eS0ye\src\backedges.jl:90
  method(::SnoopCompile.InstanceNode)
   @ SnoopCompile C:\Users\joaqu\.julia\packages\SnoopCompile\Q8zUg\src\invalidations.jl:771
  ...

With backedges, use root = tree.backedges[idx] and ascend(root). It’s described in the docs :slight_smile: Snooping on and fixing invalidations: @snoopr · SnoopCompile

What is that tree? One from trees or from taletrees? (The names in my example 2 posts above). Because, in latter case it still errors.

root = tree.backedges[1]
MethodInstance for firstindex(::AbstractString) at depth 0 with 1 children

julia> ascend(roots)
ERROR: MethodError: no method matching method(::Tuple{SnoopCompile.InstanceNode, Vector{SnoopCompileCore.InferenceTimingNode}})

The example from SnoopCompile docs makes think it should be from trees but than in my case there are lots of entries and is hard to find this particular invalidation shown in taletrees

Can you post explicit instructions I can use to reproduce this? Including GMT branch info etc.

Oh, you typed ascend(roots) but you assigned the result to root. Drop the s maybe?

Yes, some confusions with the root vs roots but I must had both vars and that made no difference.

This is with the master version (just committed all my local changes)

  | | |_| | | | (_| |  |  Version 1.10.0-DEV.221 (2022-12-29)
 _/ |\__'_|_|_|\__'_|  |  Commit 6740224b94 (7 days old master)
|__/                   |

using SnoopCompileCore
invs = @snoopr using GMT, FFTW;
tinf = @snoopi_deep GMT.plot(rand(5,2));
using SnoopCompile
trees = invalidation_trees(invs);

taletrees = precompile_blockers(trees, tinf)
1-element Vector{SnoopCompile.StaleTree}:
 inserting eltype(::Type{ChainRulesCore.ZeroTangent}) @ ChainRulesCore C:\Users\joaqu\.julia\packages\ChainRulesCore\C73ay\src\tangent_types\abstract_zero.jl:55 invalidated:
   backedges: 1: MethodInstance for eltype(::Type) at depth 0 with 26 children blocked 1.5386513 inclusive time for 2 nodes

tree = taletrees[1];
roots = tree.backedges[1];

ascend(roots)
ERROR: MethodError: no method matching method(::Tuple{SnoopCompile.InstanceNode, Vector{SnoopCompileCore.InferenceTimingNode}})

Closest candidates are:
  method(::Core.MethodInstance)
   @ Cthulhu C:\Users\joaqu\.julia\packages\Cthulhu\eS0ye\src\backedges.jl:84
  method(::Vector{Base.StackTraces.StackFrame})
   @ Cthulhu C:\Users\joaqu\.julia\packages\Cthulhu\eS0ye\src\backedges.jl:90
  method(::SnoopCompile.InstanceNode)
   @ SnoopCompile C:\Users\joaqu\.julia\packages\SnoopCompile\Q8zUg\src\invalidations.jl:771
  ...

Sorry, I forgot that the format of entries for precompile_blockers is different from invalidations: it also includes the things that got recompiled (from tinf). You want

julia> root, recompiles = tree.backedges[end]
(MethodInstance for eltype(::Type{P} where P<:(Matrix{<:Real})) at depth 0 with 10 children, SnoopCompileCore.InferenceTimingNode[InferenceTimingNode: 0.000192/1.074739 on GMT.plot(::Matrix{Float64}) with 2 direct children])

julia> ascend(root)
Choose a call for analysis (q to quit):
 >   eltype(::Type{P} where P<:(Matrix{<:Real}))
       SubArray(::IndexLinear, ::Matrix{<:Real}, ::Tuple{Base.Slice{Base.OneTo{Int64}}, Int64}, ::Tuple{Bool})
         SubArray(::Matrix{<:Real}, ::Tuple{Base.Slice{Base.OneTo{Int64}}, Int64})
...

That leads to a very odd case. The invalidation is produced by this

extrema(view(M, :, color_col)

here where M is M::Matrix{<:Real}. What is the problem in using a view?

It can’t infer the eltype as anything more concrete than <:Real. Any time you have abstract parameters you will get inference failures.

:disappointed_relieved:

Well, if your caller can pass something that is well-inferred to line2multiseg then you’re in better shape. Your snippet doesn’t include enough of the arguments to some of its callers (_helper_psx_line, common_plot_xyz, etc.) for me to see if they have abstract arguments, but generally speaking I try to fix the problem in the first caller that has concrete (non-red) argument types; you know inference worked up to that call, so perhaps the origin of the inference failure is in there.

Here’s an example:

The set_dsBB! call is the last one where the arguments were judged to be concrete. However:

julia> isconcretetype(GMTdataset)
false

julia> Base.unwrap_unionall(GMTdataset)
GMTdataset{T, N}

so a Vector{GMTdataset} has elements which themselves are not concrete (Vector{Any} is concrete, but Any is not). So as soon as you extract an element from that array, you have inference failures.

To diagnose where that happens, you have to go much farther back up the call chain, to common_plot_xyz(::String, ::Matrix{Float64}, ::String, ::Bool, ::Bool). There, if you descend into it, the suggested line to inspect

Choose possible caller of MethodInstance for GMT.with_xyvar(::Dict{Symbol, Any}, ::GMTdataset) or proceed to typed code:
 > "/home/tim/.julia/dev/GMT/src/psxy.jl", common_plot_xyz: lines [109]
   Browse typed code

has contents

(cmd0 != "" && isa(arg1, GMTdataset)) && (arg1 = with_xyvar(d::Dict, arg1))     # If we read a file, see if requested cols

This allows inference to know that arg1 is a GMTdataset but it has no idea what its type parameters are.

The good news is that you can fix this. Just change with_xyvar(d::Dict, arg1) to Base.invokelatest(with_xyvar, d, arg1). This forces it to use runtime dispatch, in which case with_xyvar will receive all-concrete arguments. This alone might be enough to fix the whole call chain. And if not, at least common_plot_xyz(::String, ::Matrix{Float64}, ::String, ::Bool, ::Bool) and its callers are protected against invalidation. (invokelatest “breaks the chain” and prevents invalidations from cascading.)

1 Like

Is the preferred approach to use invokelatest or Base.inferencebarrier? Some people seemed to prefer the latter on GitHub issues (for Julia’s own repo).

1 Like

As you surely know, they do slightly different things, though they could result in the same practical outcome:

  • inferencebarrier causes infer-as-Any, and sometimes this triggers runtime dispatch (but not for, e.g., @nospecialize args)
  • invokelatest forces runtime dispatch, but also effectively updates the world age.

What I actually think we want is a callsite annotation @concrete f(args...) to block abstract inference: indicate that we should invoke f directly if all the args can be inferred concretely, and otherwise use runtime-dispatch. But we don’t have @concrete (yet).

4 Likes

I like the idea of @concrete. Both inferencebarrier and invokelatest are hard to understand its meaning thus to remember.

Thanks! I also like the idea of @concrete

Thanks for this one more learning lesson. The case that triggered this analysis is not that important to me as I’ve replaced the extrema(view(...)) call by a little internal function that has the further advantage of not ignoring NaNs, but I have a couple more of this type and I’ll try to apply this logic to fix them.

However, I think this is a useful example to others because

julia> isconcretetype(GMTdataset)
false

is so (if I’m understanding this matters) because a GMTdataset is an abstract array, so all other types that implement the abstract array interface are candidates to suffer from these inference issues.

Unfortunately not yet.

  | | |_| | | | (_| |  |  Version 1.10.0-DEV.293 (2023-01-07)
 _/ |\__'_|_|_|\__'_|  |  Commit 8dbf7a1517 (0 days old master)
|__/                   |

julia> include("c:/v/test_gmt1.jl");

julia> trees
6-element Vector{SnoopCompile.MethodInvalidations}:
 inserting firstindex(s::LaTeXStrings.LaTeXString) @ LaTeXStrings C:\Users\joaqu\.julia\packages\LaTeXStrings\pJ7vn\src\LaTeXStrings.jl:108 invalidated:
   backedges: 1: superseding firstindex(s::AbstractString) @ Base strings\basic.jl:180 with MethodInstance for firstindex(::AbstractString) (1 children)

 inserting codeunits(s::LaTeXStrings.LaTeXString) @ LaTeXStrings C:\Users\joaqu\.julia\packages\LaTeXStrings\pJ7vn\src\LaTeXStrings.jl:124 invalidated:
   mt_backedges: 1: signature Tuple{typeof(codeunits), Any} triggered MethodInstance for Base.cwstring(::AbstractString) (3 children)
   backedges: 1: superseding codeunits(s::AbstractString) @ Base strings\basic.jl:785 with MethodInstance for codeunits(::AbstractString) (1 children)

 inserting Base.IteratorSize(::Type{R}) where R<:Union{AbstractColumns, AbstractRow} @ Tables C:\Users\joaqu\.julia\packages\Tables\T7rHm\src\Tables.jl:179 invalidated:
   backedges: 1: superseding Base.IteratorSize(::Type) @ Base generator.jl:94 with MethodInstance for Base.IteratorSize(::Type{<:AbstractString}) (5 children)
   75 mt_cache

 inserting lastindex(s::LaTeXStrings.LaTeXString) @ LaTeXStrings C:\Users\joaqu\.julia\packages\LaTeXStrings\pJ7vn\src\LaTeXStrings.jl:109 invalidated:
   backedges: 1: superseding lastindex(s::AbstractString) @ Base strings\basic.jl:181 with MethodInstance for lastindex(::AbstractString) (5 children)

 inserting sizeof(s::LaTeXStrings.LaTeXString) @ LaTeXStrings C:\Users\joaqu\.julia\packages\LaTeXStrings\pJ7vn\src\LaTeXStrings.jl:125 invalidated:
   backedges: 1: superseding sizeof(s::AbstractString) @ Base strings\basic.jl:179 with MethodInstance for sizeof(::AbstractString) (15 children)
   6 mt_cache

 inserting convert(::Type{String}, d::StringManipulation.Decoration) @ StringManipulation C:\Users\joaqu\.julia\packages\StringManipulation\wxRU4\src\decorations.jl:193 invalidated:
   mt_backedges:  1: signature Tuple{typeof(convert), Type{String}, Any} triggered MethodInstance for setindex!(::Dict{String, Base.UUID}, ::Any, ::Any) (0 children)
                  2: signature Tuple{typeof(convert), Type{String}, Any} triggered MethodInstance for setindex!(::Dict{String, Union{Bool, String}}, ::Any, ::Any) (0 children)
                  3: signature Tuple{typeof(convert), Type{String}, Any} triggered MethodInstance for setindex!(::Dict{String, Union{Nothing, String}}, ::Any, ::Any) (0 children)
                  4: signature Tuple{typeof(convert), Type{String}, Any} triggered MethodInstance for setindex!(::Dict{String, Nothing}, ::Nothing, ::Any) (0 children)
                  5: signature Tuple{typeof(convert), Type{String}, Any} triggered MethodInstance for setindex!(::Vector{String}, ::Any, ::Int64) (0 children)
                  6: signature Tuple{typeof(convert), Type{String}, Any} triggered MethodInstance for push!(::Vector{String}, ::Any) (0 children)
                  7: signature Tuple{typeof(convert), Type{String}, Any} triggered MethodInstance for setindex!(::Dict{String, Union{Base.SHA1, String}}, ::Any, ::Any) (0 children)
                  8: signature Tuple{typeof(convert), Type{String}, Any} triggered MethodInstance for setindex!(::Dict{String, Function}, ::Any, ::Any) (0 children)
                  9: signature Tuple{typeof(convert), Type{String}, Any} triggered MethodInstance for Test.Fail(::Symbol, ::Any, ::Any, ::Bool, ::Nothing, ::LineNumberNode, ::Bool) (0 children)
                 10: signature Tuple{typeof(convert), Type{String}, Any} triggered MethodInstance for Test.Fail(::Symbol, ::Any, ::Any, ::Any, ::Any, ::LineNumberNode, ::Bool) (0 children)
                 11: signature Tuple{typeof(convert), Type{String}, Any} triggered MethodInstance for Test.Fail(::Symbol, ::Any, ::Any, ::Any, ::Nothing, ::LineNumberNode, ::Bool) (0 children)
                 12: signature Tuple{typeof(convert), Type{String}, Any} triggered MethodInstance for Test.Fail(::Symbol, ::Any, ::Any, ::Nothing, ::Nothing, ::LineNumberNode, ::Bool) (0 children)
                 13: signature Tuple{typeof(convert), Type{String}, Any} triggered MethodInstance for setindex!(::Dict{String, Any}, ::Any, ::Any) (0 children)
                 14: signature Tuple{typeof(convert), Type{String}, Any} triggered MethodInstance for setindex!(::Dict{String, Union{String, Vector{String}}}, ::Any, ::Any) (0 children)
                 15: signature Tuple{typeof(convert), Type{String}, Any} triggered MethodInstance for setindex!(::Dict{String, Vector{Pkg.Types.Stage1}}, ::Vector{Pkg.Types.Stage1}, ::Any) (0 children)
                 16: signature Tuple{typeof(convert), Type{String}, Any} triggered MethodInstance for setindex!(::Dict{String, Base.UUID}, ::Base.UUID, ::Any) (0 children)
                 17: signature Tuple{typeof(convert), Type{String}, Any} triggered MethodInstance for setindex!(::Dict{String, Vector{String}}, ::Vector{String}, ::Any) (0 children)
                 18: signature Tuple{typeof(convert), Type{String}, Any} triggered MethodInstance for setindex!(::Dict{String, Pkg.Types.Compat}, ::Any, ::Any) (0 children)
                 19: signature Tuple{typeof(convert), Type{String}, Any} triggered MethodInstance for setindex!(::Dict{String, String}, ::Any, ::Any) (0 children)
                 20: signature Tuple{typeof(convert), Type{String}, Any} triggered MethodInstance for (::Base.var"#cvt1#1"{Tuple{String, String, Base.UUID}})(::Int64) (0 children)
                 21: signature Tuple{typeof(convert), Type{String}, Any} triggered MethodInstance for (::Base.var"#cvt1#1"{Tuple{String, Union{Nothing, VersionNumber}}})(::Int64) (0 children)
                 22: signature Tuple{typeof(convert), Type{String}, Any} triggered MethodInstance for (::Base.var"#cvt1#1"{Tuple{Base.UUID, String, String, VersionNumber}})(::Int64) (0 children)
                 23: signature Tuple{typeof(convert), Type{String}, Any} triggered MethodInstance for (::Base.var"#cvt1#1"{Tuple{Revise.PkgData, String}})(::Int64) (0 children)
                 24: signature Tuple{typeof(convert), Type{String}, Any} triggered MethodInstance for setindex!(::IdDict{Any, String}, ::Any, ::Any) (1 children)
                 25: signature Tuple{typeof(convert), Type{String}, Any} triggered MethodInstance for Test.Error(::Symbol, ::Any, ::Any, ::Vector{Any}, ::LineNumberNode) (1 children)
                 26: signature Tuple{typeof(convert), Type{String}, Any} triggered MethodInstance for Test.Error(::Symbol, ::Any, ::Bool, ::Nothing, ::LineNumberNode) (1 children)
                 27: signature Tuple{typeof(convert), Type{String}, Any} triggered MethodInstance for REPL.LineEditREPL(::Any, ::Any, ::Any, ::Any, ::Any, ::Any, ::Any, ::Any, ::Any, ::Any, ::Any) (1 children)
                 28: signature Tuple{typeof(convert), Type{String}, Any} triggered MethodInstance for REPL.LineEdit.PrefixSearchState(::Any, ::Any, ::Any, ::Any) (1 children)
                 29: signature Tuple{typeof(convert), Type{String}, Any} triggered MethodInstance for Logging.var"#handle_message#3"(::Base.Pairs{Symbol, Union{}, Tuple{}, NamedTuple{(), Tuple{}}}, ::typeof(Base.CoreLogging.handle_message), ::Logging.ConsoleLogger, ::Base.CoreLogging.LogLevel, ::Any, ::Any, ::Any, ::Any, ::Any, ::Any) (1 children)
                 30: signature Tuple{typeof(convert), Type{String}, Any} triggered MethodInstance for Logging.var"#handle_message#3"(::Base.Pairs{Symbol, V, Tuple{Vararg{Symbol, N}}, NamedTuple{names, T}} where {V, N, names, T<:Tuple{Vararg{Any, N}}}, ::typeof(Base.CoreLogging.handle_message), ::Logging.ConsoleLogger, ::Base.CoreLogging.LogLevel, ::Any, ::Any, ::Any, ::Any, ::Any, ::Any) (1 children)
                 31: signature Tuple{typeof(convert), Type{String}, Any} triggered MethodInstance for convert(::Type{Pair{String, String}}, ::Pair{String}) (1 children)
                 32: signature Tuple{typeof(convert), Type{String}, Any} triggered MethodInstance for setindex!(::Dict{String, String}, ::Any, ::String) (1 children)
                 33: signature Tuple{typeof(convert), Type{String}, Any} triggered MethodInstance for Test.Error(::Symbol, ::Any, ::Any, ::Nothing, ::LineNumberNode) (2 children)
                 34: signature Tuple{typeof(convert), Type{String}, Any} triggered MethodInstance for Test.Error(::Symbol, ::Any, ::Any, ::Any, ::LineNumberNode) (2 children)
                 35: signature Tuple{typeof(convert), Union{Type{Base.UUID}, Type{String}}, Any} triggered MethodInstance for (::Base.var"#cvt1#1"{Tuple{String, String, Base.UUID}})(::Int64) (3 children)
                 36: signature Tuple{typeof(convert), Union{Type{Base.UUID}, Type{VersionNumber}, Type{String}}, Any} triggered MethodInstance for (::Base.var"#cvt1#1"{Tuple{Base.UUID, String, String, VersionNumber}})(::Int64) (3 children)
                 37: signature Tuple{typeof(convert), Union{Type{String}, Type{Revise.PkgData}}, Any} triggered MethodInstance for (::Base.var"#cvt1#1"{Tuple{Revise.PkgData, String}})(::Int64) (3 children)
                 38: signature Tuple{typeof(convert), Type{String}, Any} triggered MethodInstance for Base.CoreLogging.var"#handle_message#2"(::Base.Pairs{Symbol, V, Tuple{Vararg{Symbol, N}}, NamedTuple{names, T}} where {V, N, names, T<:Tuple{Vararg{Any, N}}}, ::typeof(Base.CoreLogging.handle_message), ::Base.CoreLogging.SimpleLogger, ::Base.CoreLogging.LogLevel, ::Any, ::Any, ::Any, ::Any, ::Any, ::Any) (4 children)
                 39: signature Tuple{typeof(convert), Union{Type{String}, Type{Union{Nothing, VersionNumber}}}, Any} triggered MethodInstance for (::Base.var"#cvt1#1"{Tuple{String, Union{Nothing, VersionNumber}}})(::Int64) (4 children)
                 40: signature Tuple{typeof(convert), Type{String}, Any} triggered MethodInstance for REPL.REPLHistoryProvider(::Any, ::Any, ::Any, ::Any, ::Any, ::Any, ::Any, ::Any, ::Any, ::Any) (17 children)
                 41: signature Tuple{typeof(convert), Type{String}, Any} triggered MethodInstance for Pkg.Registry.verify_compressed_registry_toml(::String) (47 children)
                 42: signature Tuple{typeof(convert), Type{String}, Any} triggered MethodInstance for convert(::Type{Union{Nothing, String}}, ::Any) (48 children)
                 43: signature Tuple{typeof(convert), Type{String}, Any} triggered MethodInstance for Pkg.Types.Compat(::Pkg.Versions.VersionSpec, ::Any) (230 children)

Pkg has not been updated on master yet.

Ah, good (only looked at the merge status of that PR).