JuliaLang / julia

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

BUG: Unnecessary allocations when including `<:` for abstract type in `Union` for a keyword argument #53917

Open mattsignorelli opened 5 months ago

mattsignorelli commented 5 months ago

I originally posted this here in the discourse.

Consider this function which simply returns the argument at the specified index idx or y in the vector v:

function foo(v::Vector{Int}, idx::Union{Nothing,Int}=nothing; y::Union{Nothing,Integer}=nothing)
  if !xor(isnothing(idx), isnothing(y))
  error("invalid arguments")
  end

  if isnothing(y)
    return v[idx]
  else
    return v[y]
  end
end

With Benchmark tools we see:

julia> a = [1,2,3,4,5,6,7,8,9,10];

julia> @btime foo($a,1);
  1.375 ns (0 allocations: 0 bytes)

julia> @btime foo($a,y=1);
  1.333 ns (0 allocations: 0 bytes)

No surprises here, zero allocations. Now let’s make one tiny change and let the keyword argument type be Union{Nothing, <:Integer} instead of Union{Nothing, Integer}, so:

function foo2(v::Vector{Int}, idx::Union{Nothing,Int}=nothing; y::Union{Nothing,<:Integer}=nothing)
  if !xor(isnothing(idx), isnothing(y))
  error("invalid arguments")
  end

  if isnothing(y)
    return v[idx]
  else
    return v[y]
  end
end

Now we see with foo2

julia> @btime foo2($a,1);
  1.333 ns (0 allocations: 0 bytes)

julia> @btime foo2($a,y=1);
  184.793 ns (3 allocations: 96 bytes)

Julia was installed through Juliaup. My versioninfo():

julia> versioninfo()
Julia Version 1.10.2
Commit bd47eca2c8a (2024-03-01 10:14 UTC)
Build Info:
  Official https://julialang.org/ release
Platform Info:
  OS: macOS (arm64-apple-darwin22.4.0)
  CPU: 24 × Apple M2 Ultra
  WORD_SIZE: 64
  LIBM: libopenlibm
  LLVM: libLLVM-15.0.7 (ORCJIT, apple-m1)
Threads: 1 default, 0 interactive, 1 GC (on 16 virtual cores)
Environment:
  DYLD_FALLBACK_LIBRARY_PATH = /Users/mgs255/Software/bmad/bmad-ecosystem/production/lib:/Users/mgs255/Software/bmad/bmad-ecosystem/debug/lib
  LD_LIBRARY_PATH = /Users/mgs255/Software/bmad/bmad-ecosystem/production/lib:/Users/mgs255/Software/bmad/bmad-ecosystem/debug/lib
lmiq commented 5 months ago

Minimal example:

julia> foo(v; y::Union{Nothing,<:Integer}=nothing) = isnothing(y) ? v[1] : v[y]
foo (generic function with 1 method)

julia> @btime foo($[1,2,3];y=1);
  286.996 ns (3 allocations: 96 bytes)

julia> foo(v; y::Union{Nothing,Integer}=nothing) = isnothing(y) ? v[1] : v[y]
foo (generic function with 1 method)

julia> @btime foo($[1,2,3];y=1);
  2.609 ns (0 allocations: 0 bytes)

julia> foo(v; y::Union{Nothing,T}=nothing) where {T<:Integer} = isnothing(y) ? v[1] : v[y]
foo (generic function with 1 method)

julia> @btime foo($[1,2,3];y=1);
  3.002 ns (0 allocations: 0 bytes)