JuliaLang / julia

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

Mini-julep: use typed Dict key/value types in Pair construction #37098

Open timholy opened 4 years ago

timholy commented 4 years ago

Currently, the syntax a => b creates the narrowest typed-Pair for a and b. However, when this pair is being used to construct a heterogeneous Dict, this isn't exactly what you want: you'd prefer the pairs to be created from the outset with the Dict's key and value type:

julia> f() = Dict{Symbol,Any}(:a => 1, :b => "hello")
f (generic function with 1 method)

julia> code_typed(f, ())
1-element Vector{Any}:
 CodeInfo(
1 ─ %1 = %new(Pair{Symbol,Int64}, :a, 1)::Pair{Symbol,Int64}
│   %2 = %new(Pair{Symbol,String}, :b, "hello")::Pair{Symbol,String}
│   %3 = invoke Dict{Symbol,Any}(%1::Pair{Symbol,Int64}, %2::Vararg{Pair,N} where N)::Dict{Symbol,Any}
└──      return %3
) => Dict{Symbol,Any}

julia> @btime f()
  195.619 ns (6 allocations: 672 bytes)
Dict{Symbol,Any} with 2 entries:
  :a => 1
  :b => "hello"

is not as nice (in terms of generated code) as

julia> ft() = Dict{Symbol,Any}(Pair{Symbol,Any}(:a, 1), Pair{Symbol,Any}(:b, "hello"))
ft (generic function with 1 method)

julia> code_typed(ft, ())
1-element Vector{Any}:
 CodeInfo(
1 ─ %1 = %new(Pair{Symbol,Any}, :a, 1)::Pair{Symbol,Any}
│   %2 = %new(Pair{Symbol,Any}, :b, "hello")::Pair{Symbol,Any}
│   %3 = invoke Dict{Symbol,Any}(%1::Pair{Symbol,Any}, %2::Vararg{Pair{Symbol,Any},N} where N)::Dict{Symbol,Any}
└──      return %3
) => Dict{Symbol,Any}

julia> @btime ft()
  135.983 ns (6 allocations: 672 bytes)
Dict{Symbol,Any} with 2 entries:
  :a => 1
  :b => "hello"

We could have a @Dict that performs this operation, but before implementing this, is there any chance this is something that should just be done by the parser?

simeonschaub commented 4 years ago

An alternative might be explicitly converting the pairs in the Dict constructor like this:

julia> struct Dict2{K,V}
           Dict2{K,V}(x::Pair...) where {K,V} = Dict{K,V}(map(p -> convert(Pair{K,V}, p), x)::Tuple{Vararg{Pair{K,V}}}...)
       end

julia> code_typed(f2, ())
1-element Vector{Any}:
 CodeInfo(
1 ─ %1 = %new(Pair{Symbol,Any}, :a, 1)::Pair{Symbol,Any}
│   %2 = %new(Pair{Symbol,Any}, :b, "hello")::Pair{Symbol,Any}
│   %3 = invoke Dict{Symbol,Any}(%1::Pair{Symbol,Any}, %2::Vararg{Pair{Symbol,Any},N} where N)::Dict{Symbol,Any}
└──      return %3
) => Dict{Symbol,Any}

julia> @btime f()
  216.752 ns (6 allocations: 672 bytes)
Dict{Symbol,Any} with 2 entries:
  :a => 1
  :b => "hello"

julia> @btime ft()
  165.954 ns (6 allocations: 672 bytes)
Dict{Symbol,Any} with 2 entries:
  :a => 1
  :b => "hello"

julia> @btime f2()
  159.693 ns (6 allocations: 672 bytes)
Dict{Symbol,Any} with 2 entries:
  :a => 1
  :b => "hello"

Although I don't know whether that would still be as fast if the pairs are not just constants.

JeffBezanson commented 4 years ago

is there any chance this is something that should just be done by the parser

No :smile: