Identical method redefinition suspiciously optimizes runtime and allocations

First off, that’s impressive. Only thing I would suggest is commenting out multiple lines between #= and =# instead of # leading each line to make copy-pasting convenient.

Second, I tried @code_warntype right out the gate before any execution, and it appears as type-stable as your reports after benchmarks. However, if we Cthulhu.@descend deeper, we run into recursion with changing argument types:

305  all(f::Main.Euler2D_MWE.var"#1#2"{SClosedPolygon{4, Float64}}::Function, a::SVector{4, SVector{2, Float64}}::StaticArray; dims::Core.Const(Colon())=:)::Bool = _mapreduce(x->f(x)::Bool, &::Core.Const(&), dims::Core.Const(Colon()), true, Size(a::SVector{4, SVector{2, Float64}})::Core.Const(Size(4,)), a::SVector{4, SVector{2, Float64}})
Select a call to descend into or ↩ to ascend. [q]uit. [b]ookmark.
Toggles: [w]arn, [h]ide type-stable statements, [t]ype annotations, [s]yntax highlight for Source/LLVM/Native, [j]ump to source always.
Show: [S]ource code, [A]ST, [T]yped code, [L]LVM IR, [N]ative code
Actions: [E]dit source code, [R]evise and redisplay
   Size(a::SVector{4, SVector{2, Float64}})
 β€’ _mapreduce(x->f(x)::Bool, &::Core.Const(&), dims::Core.Const(Colon()), true, Size(a::SVector{4, SVector{2, Float64}})::Core.Const(Size(4,)), a::SVec…
   ↩
_mapreduce(args::Vararg{Any, N}) where N @ StaticArrays C:\Users\Benny\.julia\packages\StaticArrays\DsPgf\src\mapreduce.jl:153
β”Œ Warning: Inference terminated in an incomplete state due to argument-type changes during recursion

despite the ultimate return value of all being inferred as Bool. The implied recursion isn’t clear because some metaprogramming obscured calls on top of the compromised type information; in other words, it’s hard to pick which lines to manually descend further.

Fortunately, `JET.@reportopt` automatically ''descends'' into all the calls to filter out instability and dispatch issues, and it spots an explicit recursion becoming unoptimized and runtime-dispatched. It seems to corroborate your comment about `all(Base.Fix2(...),...)` being a potential issue for `point_inside_strict`, a function called in the input function for `all` in the benchmarked function.
julia> @report_opt Euler2D_MWE.is_cell_contained_by(test_cell, test_poly1)
═════ 8 possible errors found ═════
β”Œ is_cell_contained_by(cell1::TangentQuadCell_MWE{Float64, 3, 12}, poly::SClosedPolygon{4, Float64}) @ Main.Euler2D_MWE ./REPL[7]:35
β”‚β”Œ all(f::Main.Euler2D_MWE.var"#1#2"{SClosedPolygon{4, Float64}}, a::SVector{4, SVector{2, Float64}}) @ StaticArrays C:\Users\Benny\.julia\packages\StaticArrays\DsPgf\src\mapreduce.jl:305
β”‚β”‚β”Œ all(f::Main.Euler2D_MWE.var"#1#2"{SClosedPolygon{4, Float64}}, a::SVector{4, SVector{2, Float64}}; dims::Colon) @ StaticArrays C:\Users\Benny\.julia\packages\StaticArrays\DsPgf\src\mapreduce.jl:305
β”‚β”‚β”‚β”Œ _mapreduce(::StaticArrays.var"#214#215"{…}, ::typeof(&), ::Colon, ::Bool, ::Size{…}, ::SVector{…}) @ StaticArrays C:\Users\Benny\.julia\packages\StaticArrays\DsPgf\src\mapreduce.jl:153
β”‚β”‚β”‚β”‚β”Œ _mapfoldl(f::StaticArrays.var"#214#215"{…}, op::typeof(&), dims::Colon, init::Bool, ::Size{…}, a::SVector{…}) @ StaticArrays C:\Users\Benny\.julia\packages\StaticArrays\DsPgf\src\mapreduce.jl:180
β”‚β”‚β”‚β”‚β”‚β”Œ (::StaticArrays.var"#214#215"{Main.Euler2D_MWE.var"#1#2"{SClosedPolygon{4, Float64}}})(x::SVector{2, Float64}) @ StaticArrays C:\Users\Benny\.julia\packages\StaticArrays\DsPgf\src\mapreduce.jl:305
β”‚β”‚β”‚β”‚β”‚β”‚β”Œ (::Main.Euler2D_MWE.var"#1#2"{SClosedPolygon{4, Float64}})(pt::SVector{2, Float64}) @ Main.Euler2D_MWE ./REPL[7]:36
β”‚β”‚β”‚β”‚β”‚β”‚β”‚β”Œ point_inside_strict(poly::SClosedPolygon{4, Float64}, pt::SVector{2, Float64}) @ Main.PlanePolygons_MWE ./REPL[6]:71
β”‚β”‚β”‚β”‚β”‚β”‚β”‚β”‚β”Œ all(f::Base.Fix2{…}, a::SVector{…}) @ StaticArrays C:\Users\Benny\.julia\packages\StaticArrays\DsPgf\src\mapreduce.jl:305
β”‚β”‚β”‚β”‚β”‚β”‚β”‚β”‚β”‚β”Œ all(f::Base.Fix2{…}, a::SVector{…}; dims::Colon) @ StaticArrays C:\Users\Benny\.julia\packages\StaticArrays\DsPgf\src\mapreduce.jl:305
β”‚β”‚β”‚β”‚β”‚β”‚β”‚β”‚β”‚β”‚β”Œ _mapreduce(::StaticArrays.var"#214#215", ::typeof(&), ::Colon, ::Bool, ::Size{…}, ::SVector{…}) @ StaticArrays C:\Users\Benny\.julia\packages\StaticArrays\DsPgf\src\mapreduce.jl:153
β”‚β”‚β”‚β”‚β”‚β”‚β”‚β”‚β”‚β”‚β”‚ runtime dispatch detected: StaticArrays._mapfoldl(%1::StaticArrays.var"#214#215", &, Colon(), %2::Bool, [quote]::Size{(4,)}, %3::SVector{4, Main.PlanePolygons_MWE.Line{Float64}})::Bool
││││││││││└────────────────────
β”‚β”‚β”‚β”‚β”‚β”‚β”‚β”‚β”‚β”Œ all(f::Base.Fix2{…}, a::SVector{…}; dims::Colon) @ StaticArrays C:\Users\Benny\.julia\packages\StaticArrays\DsPgf\src\mapreduce.jl:305
β”‚β”‚β”‚β”‚β”‚β”‚β”‚β”‚β”‚β”‚ failed to optimize due to recursion: StaticArrays.var"#all#213"(::Colon, ::typeof(all), ::Base.Fix2{…}, ::SVector{…})
│││││││││└────────────────────
β”‚β”‚β”‚β”‚β”‚β”‚β”‚β”‚β”Œ all(f::Base.Fix2{…}, a::SVector{…}) @ StaticArrays C:\Users\Benny\.julia\packages\StaticArrays\DsPgf\src\mapreduce.jl:305
β”‚β”‚β”‚β”‚β”‚β”‚β”‚β”‚β”‚ failed to optimize due to recursion: all(::Base.Fix2{…}, ::SVector{…})
││││││││└────────────────────
β”‚β”‚β”‚β”‚β”‚β”‚β”‚β”Œ point_inside_strict(poly::SClosedPolygon{4, Float64}, pt::SVector{2, Float64}) @ Main.PlanePolygons_MWE ./REPL[6]:70
β”‚β”‚β”‚β”‚β”‚β”‚β”‚β”‚ failed to optimize due to recursion: Main.PlanePolygons_MWE.point_inside_strict(::SClosedPolygon{4, Float64}, ::SVector{2, Float64})
│││││││└────────────────────
β”‚β”‚β”‚β”‚β”‚β”‚β”Œ (::Main.Euler2D_MWE.var"#1#2"{SClosedPolygon{4, Float64}})(pt::SVector{2, Float64}) @ Main.Euler2D_MWE ./REPL[7]:36
β”‚β”‚β”‚β”‚β”‚β”‚β”‚ failed to optimize due to recursion: (::Main.Euler2D_MWE.var"#1#2"{SClosedPolygon{4, Float64}})(::SVector{2, Float64})
││││││└────────────────────
β”‚β”‚β”‚β”‚β”‚β”Œ (::StaticArrays.var"#214#215"{Main.Euler2D_MWE.var"#1#2"{SClosedPolygon{4, Float64}}})(x::SVector{2, Float64}) @ StaticArrays C:\Users\Benny\.julia\packages\StaticArrays\DsPgf\src\mapreduce.jl:305
β”‚β”‚β”‚β”‚β”‚β”‚ failed to optimize due to recursion: (::StaticArrays.var"#214#215"{Main.Euler2D_MWE.var"#1#2"{SClosedPolygon{4, Float64}}})(::SVector{2, Float64})
│││││└────────────────────
β”‚β”‚β”‚β”‚β”Œ _mapfoldl(f::StaticArrays.var"#214#215"{…}, op::typeof(&), dims::Colon, init::Bool, ::Size{…}, a::SVector{…}) @ StaticArrays C:\Users\Benny\.julia\packages\StaticArrays\DsPgf\src\mapreduce.jl:155
β”‚β”‚β”‚β”‚β”‚ failed to optimize due to recursion: StaticArrays._mapfoldl(::StaticArrays.var"#214#215"{…}, ::typeof(&), ::Colon, ::Bool, ::Size{…}, ::SVector{…})
││││└────────────────────
β”‚β”‚β”‚β”Œ _mapreduce(::StaticArrays.var"#214#215"{…}, ::typeof(&), ::Colon, ::Bool, ::Size{…}, ::SVector{…}) @ StaticArrays C:\Users\Benny\.julia\packages\StaticArrays\DsPgf\src\mapreduce.jl:153
β”‚β”‚β”‚β”‚ failed to optimize due to recursion: StaticArrays._mapreduce(::StaticArrays.var"#214#215"{…}, ::typeof(&), ::Colon, ::Bool, ::Size{…}, ::SVector{…})
│││└────────────────────

These reports persist after the 0 allocation benchmark. These reports also show up if run fresh after your MWE completed the 0 allocation benchmark. Like @code_warntype etc, these packages seem to perform type inference fresh, so it’s still possible it diverges from the actually compiled code. Not great in the exceptional cases like these where state matters.

3 Likes

A workaround is to convert the SArray to a Tuple before calling all:

all(Tuple(edge_starts(cell_poly)))

This avoids the ancient and hacky StaticArrays.jl code, instead relying on Base Julia code.

1 Like

Haven’t examined it, but do you know how introducing that Tuple conversion avoids the type-varying recursion issue? It ought to have the same recursive all via the input function, right?

Converting to Tuple avoids the method of all added by StaticArrays.jl.

See:

Too quick to disrespect StaticArrays.jl :sweat_smile:

After a few hours I’m still not able to make a PR to StaticArrays.jl to fix the issue. Some observations:

  • The input array to all is a nested static array, with static arrays as elements. This complicates the recursion, making inference give up.

  • The all for Tuple does not have to support a dims argument, simplifying the problem.

My conclusion, for now, is to convert static arrays to tuples as eagerly as possible.

I guess the important thing about the workaround is that the conversion to tuple happens before any all(f, c) call.

2 Likes

Thanks for the help! Converting the SArray to a tuple before feeding it to all works like a charm. Benchmarks on my machine are even faster, down to 13ns instead of 40ns.

I’ll have to write specializations for boundary polygons that don’t have a compile-time number of vertices, but that’s okay.

1 Like