Skip to content

Conversation

@Keno
Copy link
Member

@Keno Keno commented Jul 3, 2020

Currently abstract iteration works fine for length-1 iterators, but
fails over for other small length iterators, such as Pair{Int, Int},
or common patterns where iterate iterates over the fields of a
small struct. Other examples include StaticVectors and constant
iterators, which should all now unroll properly up to the
MAX_TUPLE_SPLAT limit. That said, MAX_TUPLE_SPLAT is quite high
at the moment, but because abstract_iteration isn't very precise,
it's unlikely to be limiting. With this increase in precision,
we may find that MAX_TUPLE_SPLAT is too high and we should lower it.

Also note that while this is a nice improvement, performance is still
not great, since we currently can't inline these apply calls. However,
fixing that is in progress in a parallel PR and with both changes
put together, the performance improvement of splatting of small
iterators is quite sizeable.

@Keno Keno force-pushed the kf/abstract_iterate_precision branch from a8b1eb5 to e272548 Compare July 3, 2020 06:14
@Keno Keno added the compiler:inference Type inference label Jul 3, 2020
@Keno
Copy link
Member Author

Keno commented Jul 3, 2020

Also note that while this is a nice improvement, performance is still
not great, since we currently can't inline these apply calls. However,
fixing that is in progress in a parallel PR and with both changes
put together, the performance improvement of splatting of small
iterators is quite sizeable.

Some numbers on this:

Before

julia> f(x) = (x...,)
f (generic function with 1 method)

julia> @benchmark f(1=>2)
BenchmarkTools.Trial: 
  memory estimate:  96 bytes
  allocs estimate:  3
  --------------
  minimum time:     351.580 ns (0.00% GC)
  median time:      357.165 ns (0.00% GC)
  mean time:        373.367 ns (0.97% GC)
  maximum time:     9.967 μs (96.17% GC)
  --------------
  samples:          10000
  evals/sample:     212

After:

julia> @benchmark f(1=>2)
BenchmarkTools.Trial: 
  memory estimate:  0 bytes
  allocs estimate:  0
  --------------
  minimum time:     2.026 ns (0.00% GC)
  median time:      2.032 ns (0.00% GC)
  mean time:        2.035 ns (0.00% GC)
  maximum time:     6.816 ns (0.00% GC)
  --------------
  samples:          10000
  evals/sample:     1000

@martinholters
Copy link
Member

Something I long had on my to do list, but a half-hearted attempt didn't work out, so it stayed there. Glad you tackled it!

I wouldn't worry too much about the high MAX_TUPLE_SPLAT: For those cases where this PR makes a difference, i.e. where iterate returns a const-inferable state, it is also probably quite simple and therefore cheap to infer/const-propagate. Depending on the context, the more precise inference result might make up for the extra work. The worst case is probably where MAX_TUPLE_SPLAT is exceeded: Extra work with little profit. Code splatting lots of large StaticArrays might take a hit? A middle ground might be to have a second, lower cut-off and start widenconsting (i.e. like before this PR) once that is exceeded. But let's first see whether this causes any relevant latency issues as is.

Copy link
Member

@martinholters martinholters left a comment

Choose a reason for hiding this comment

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

I have a proposal for fixing the (1:33...,) case which also fixes the abstractarray test, see suggestions.

@martinholters
Copy link
Member

martinholters commented Jul 3, 2020

I've noticed something else funny going on here: After include("compiler/compiler.jl"), displaying the result of splatting into tuple produces

Internal error: encountered unexpected error in runtime:
TypeError(func=:Tuple, context="parameter", expected=Type, got=Core.Compiler.Const(val=2, actual=false))
jl_type_error_rt at ../src/rtutils.c:118
jl_f_apply_type at ../src/builtins.c:962
jl_apply at ../src/julia.h:1712 [inlined]
do_apply at ../src/builtins.c:655
argtypes_to_type at ./compiler/typeutils.jl:48
abstract_call_known at ./compiler/abstractinterpretation.jl:931
abstract_call at ./compiler/abstractinterpretation.jl:955
abstract_call at ./compiler/abstractinterpretation.jl:939
abstract_eval_statement at ./compiler/abstractinterpretation.jl:1061
typeinf_local at ./compiler/abstractinterpretation.jl:1300
typeinf_nocycle at ./compiler/abstractinterpretation.jl:1371
typeinf at ./compiler/typeinfer.jl:12
abstract_call_method_with_const_args at ./compiler/abstractinterpretation.jl:289
unknown function (ip: 0x7f6dcb9b1af7)
abstract_call_gf_by_type at ./compiler/abstractinterpretation.jl:139
abstract_call_known at ./compiler/abstractinterpretation.jl:932
abstract_call at ./compiler/abstractinterpretation.jl:955
[...]
jl_compile_method_internal at ../src/gf.c:2074
jl_compile_method_internal at ../src/gf.c:2037 [inlined]
_jl_invoke at ../src/gf.c:2344 [inlined]
jl_apply_generic at ../src/gf.c:2535
display at ../stdlib/v1.6/REPL/src/REPL.jl:218
display at ../stdlib/v1.6/REPL/src/REPL.jl:232
display at ./multimedia.jl:328

E.g.

julia> f() = (()...,)
f (generic function with 1 method)

julia> f();

julia> f()
Internal error: encountered unexpected error in runtime:
TypeError(func=:Tuple, context="parameter", expected=Type, got=Core.Compiler.Const(val=2, actual=false))
[...]
()

julia> @code_typed f() # also here
CodeInfo(
1return Internal error: encountered unexpected error in runtime:
TypeError(func=:Tuple, context="parameter", expected=Type, got=Core.Compiler.Const(val=2, actual=false))
[...]
()
) => Tuple{}

The argtypes given to argtypes_to_type in these cases are

Array{Any, (3,)}[
  Core.Compiler.Const(val=typeof(Base.iterate)(), actual=false),
  Core.Compiler.Const(val=("#= circular reference @-", 1, " =#"), actual=false),
  Core.Compiler.Const(val=2, actual=false)]

and get widened to

Array{Any, (3,)}[
  typeof(Base.iterate),
  Tuple{String, Int64, String},
  Core.Compiler.Const(val=2, actual=false)]

No idea what to make of this...

EDIT: This doesn't depend on how the value to show produced, just what is show, e.g.

julia> ()
TypeError(func=:Tuple, context="parameter", expected=Type, got=Core.Compiler.Const(val=2, actual=false))
[...]
()

@martinholters
Copy link
Member

Ah, maybe the Core.Compiler.Const(val=2, actual=false) is a result cached from before reloading compiler.jl and hence is not recognized by widenconst? And this is to be expected and include("compiler/compiler.jl") is just a don't-do-that? If so sorry for the noise.

@Keno Keno force-pushed the kf/abstract_iterate_precision branch from e272548 to a1bdc2f Compare July 3, 2020 20:01
@Keno
Copy link
Member Author

Keno commented Jul 3, 2020

After include("compiler/compiler.jl"), displaying the result of splatting into tuple produces

I can't reproduce this. I usually just use using Revise; Revise.track(Core.Compiler) which works fine most of the time.

@Keno
Copy link
Member Author

Keno commented Jul 3, 2020

Anyway, I rotated the second loop, which I think should address the bug you found.

@Keno Keno requested a review from martinholters July 3, 2020 20:05
Copy link
Member

@martinholters martinholters left a comment

Choose a reason for hiding this comment

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

LGTM. Should we add an explicit test for (1:N...,) with N > MAX_TUPLE_SPLAT?

@Keno
Copy link
Member Author

Keno commented Jul 8, 2020

I've noticed something else funny going on here: After include("compiler/compiler.jl"), displaying the result of splatting into tuple produces

This will be fixed by #36570

@Keno Keno force-pushed the kf/partialedges branch from 408289a to a2de044 Compare July 8, 2020 21:16
Base automatically changed from kf/partialedges to master July 9, 2020 00:53
Currently abstract iteration works fine for length-1 iterators, but
fails over for other small length iterators, such as Pair{Int, Int},
or common patterns where `iterate` iterates over the fields of a
small struct. Other examples include StaticVectors and constant
iterators, which should all now unroll properly up to the
MAX_TUPLE_SPLAT limit. That said, MAX_TUPLE_SPLAT is quite high
at the moment, but because abstract_iteration isn't very precise,
it's unlikely to be limiting. With this increase in precision,
we may find that MAX_TUPLE_SPLAT is too high and we should lower it.

Also note that while this is a nice improvement, performance is still
not great, since we currently can't inline these apply calls. However,
fixing that is in progress in a parallel PR and with both changes
put together, the performance improvement of splatting of small
iterators is quite sizeable.
@Keno Keno force-pushed the kf/abstract_iterate_precision branch from a1bdc2f to 3531e2a Compare July 9, 2020 07:25
@Keno Keno merged commit bee6546 into master Jul 9, 2020
@Keno Keno deleted the kf/abstract_iterate_precision branch July 9, 2020 18:19
simeonschaub pushed a commit to simeonschaub/julia that referenced this pull request Aug 11, 2020
Currently abstract iteration works fine for length-1 iterators, but
fails over for other small length iterators, such as Pair{Int, Int},
or common patterns where `iterate` iterates over the fields of a
small struct. Other examples include StaticVectors and constant
iterators, which should all now unroll properly up to the
MAX_TUPLE_SPLAT limit. That said, MAX_TUPLE_SPLAT is quite high
at the moment, but because abstract_iteration isn't very precise,
it's unlikely to be limiting. With this increase in precision,
we may find that MAX_TUPLE_SPLAT is too high and we should lower it.

Also note that while this is a nice improvement, performance is still
not great, since we currently can't inline these apply calls. However,
fixing that is in progress in a parallel PR and with both changes
put together, the performance improvement of splatting of small
iterators is quite sizeable.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

compiler:inference Type inference

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants