Skip to content

Inference of view may take very long #20848

@martinholters

Description

@martinholters

I couldn't find an issue specific to this, but ref #20832 (especially #20832 (comment)) and #20334 (comment).

Basic example:

julia> @time view(1:5,[2 3 4 1]);
 21.413390 seconds (90.22 M allocations: 4.049 GiB, 3.75% gc time)

julia> @time view(1:5,[2 3 4 1]);
  0.000080 seconds (17 allocations: 592 bytes)

In this case, it is important that this happens in global scope:

# in a new session
julia> foo() = view(1:5,[2 3 4 1])
foo (generic function with 1 method)

julia> @time foo();
  0.086676 seconds (60.93 k allocations: 2.811 MiB)

julia> @time foo();
  0.000005 seconds (8 allocations: 352 bytes)

Does the REPL force inference of view for other types than the actual parameter types or why is that?

Also note that this is responsible for an arbitrary restriction in abstract_apply being necessary. Running make test-subarray on master, I get

Test (Worker) | Time (s) | GC (s) | GC % | Alloc (MB) | RSS (MB)
subarray (1)  |  257.68  |  8.81  |  3.4 | 14845.79   | 748.68  

Limiting that restriction just to view with

--- a/base/inference.jl
+++ b/base/inference.jl
@@ -1474,7 +1474,7 @@ function abstract_apply(af::ANY, fargs, aargtypes::Vector{Any}, vtypes::VarTable
     splitunions = countunionsplit(aargtypes) <= sv.params.MAX_APPLY_UNION_ENUM
     ctypes = Any[Any[]]
     for i = 1:nargs
-        if aargtypes[i] === Any
+        if aargtypes[i] === Any && istopfunction(_topmod(sv), af, :view)
             # bail out completely and infer as f(::Any...)
             # instead could keep what we got so far and just append a Vararg{Any} (by just
             # using the normal logic from below), but that makes the time of the subarray

I get

Test (Worker) | Time (s) | GC (s) | GC % | Alloc (MB) | RSS (MB)
subarray (1)  |  267.43  | 10.15  |  3.8 | 17928.75   | 742.89  

A little longer, so a little more time spent in inference not compensated for by better run-time due to improved type-stability. Not too bad, not too surprising. But compare with removing that restriction altogether with

--- a/base/inference.jl
+++ b/base/inference.jl
@@ -1474,14 +1474,6 @@ function abstract_apply(af::ANY, fargs, aargtypes::Vector{Any}, vtypes::VarTable
     splitunions = countunionsplit(aargtypes) <= sv.params.MAX_APPLY_UNION_ENUM
     ctypes = Any[Any[]]
     for i = 1:nargs
-        if aargtypes[i] === Any
-            # bail out completely and infer as f(::Any...)
-            # instead could keep what we got so far and just append a Vararg{Any} (by just
-            # using the normal logic from below), but that makes the time of the subarray
-            # test explode
-            ctypes = Any[Any[Vararg{Any}]]
-            break
-        end
         ctypes´ = []
         for ti in (splitunions ? uniontypes(aargtypes[i]) : Any[aargtypes[i]])
             cti = precise_container_type(fargs[i], ti, vtypes, sv)

Now that gives... [drumroll]...

Test (Worker) | Time (s) | GC (s) | GC % | Alloc (MB) | RSS (MB)
subarray (1)  | 2274.95  | 66.29  |  2.9 | 43535.07   | 1424.08 

So inferring view more often for more specific argument types than just Any... (but still ending with Any...) has a dramatic impact.

I conjecture it is worthwhile figuring out the root cause here, to either (preferably) improve inference (add some less arbitrary limitations maybe) or obtain some lessons on how to write inference-friendly code and apply those to the view machinery.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions