JuliaLang / julia

The Julia Programming Language
https://julialang.org/
MIT License
45.44k stars 5.46k forks source link

Constant propagation through broadcast #43333

Open mikmoore opened 2 years ago

mikmoore commented 2 years ago

It would be nice if constant propagation worked through broadcasting.

using BenchmarkTools
get2(x) = getindex(x,2)
foo1(x) = sum(get2.(x))
foo2(x) = sum(getindex.(x,2))
x = fill((1,2.0,3//1),1000) # eltype(eltype(x)) is not concrete
@code_warntype foo1(x) # succeeds inference
@code_warntype foo2(x) # fails inference
@btime foo1($x); # 762.069 ns (1 allocation: 7.94 KiB)
@btime foo2($x); # 26.800 μs (2000 allocations: 70.39 KiB)

This fails because getindex(::Tuple{A,B,C},i::Int) where {A,B,C} is unstable unless A==B==C or i gets constant-folded.

One can work around this via the get2 solution above or a similar broadcast(...) do block (and obviously sum(get2,x) is the best solution in this MWE), but having broadcast natively recognize and propagate scalar constants would be convenient and prevent people from unknowingly falling into this trap. The primary cases where I've gotten into trouble with this are broadcasts over getindex and getproperty.

N5N3 commented 2 years ago

Our broadcast support const-propagation. If you look-into @code_typed. You will find:

11 ┄ %38 = Base.Broadcast.ifelse(%10, %21, 1)::Int64
│    %39 = Base.arrayref(false, x, %38)::Tuple{Int64, Float64, Rational{Int64}}
│    %40 = Base.getfield(%39, 2, true)::Float64

The problem is that during broadcast, we call copy, which checks the return type of bc::Broadcasted first, if it's concrete then falls-back to copyto!. IIUC, the current combine_eltypes is pure-type based, and of-cource it fails to return Float64.