Should broadcasting be avoided for type unstable code?

This simple expression incurs an overhead of 0.13 seconds in a fresh REPL (Julia 1.7.1).

julia> @time (x -> x .+ 1)(Any[2])
  0.128635 seconds (458.42 k allocations: 25.632 MiB, 11.29% gc time, 99.23% compilation time)
1-element Vector{Int64}:
 3

I’m surprised that such short code can need so much time to compile, I imagine how many of these come together in normal code bases. Our problem in Makie is that we often have to deal with type instabilities, so I’m wondering if we should generally avoid broadcasting around such code, and what other anti-patterns might be.

Are you sure the broadcasting is the problem there?

julia> @time (x -> x .+ 1)(Any[2])
  0.094409 seconds (427.71 k allocations: 24.001 MiB, 14.06% gc time, 95.70% compilation time)
1-element Vector{Int64}:
 3

julia> function g(x)
           h = x -> x .+ 1
           return h(x)
       end
g (generic function with 1 method)

julia> @time g(Any[2])
  0.000027 seconds (10 allocations: 464 bytes)
1-element Vector{Int64}:
 3

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

julia> @time f(Any[2])
  0.000021 seconds (10 allocations: 464 bytes)
1-element Vector{Int64}:
 3

Seems more related to fact that the function itself is a type-unstable object:

julia> f = x -> x .+ 1
#1 (generic function with 1 method)

julia> @time f(Any[2])
  0.075440 seconds (426.73 k allocations: 23.939 MiB, 16.28% gc time, 99.89% compilation time)
1-element Vector{Int64}:
 3


I think you can only test the latency once per session, after that some broadcasting machinery will be compiled

1 Like

True, still the other options are faster:

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

julia> @time f(Any[2])
  0.015672 seconds (33.22 k allocations: 1.776 MiB, 99.64% compilation time)
1-element Vector{Int64}:
 3

Actually here:

julia> @time let
           f(x) = x .+ 1
           f(Any[2])
       end
  0.097507 seconds (427.71 k allocations: 24.001 MiB, 13.89% gc time, 96.22% compilation time)
1-element Vector{Int64}:
 3

I get the same result as yours. Is some compilation of the machinery occurring when the function is initially defined (in the previous example). That surprises me.