# Slowness of fieldnames and propertynames

**URL:** <https://discourse.julialang.org/t/slowness-of-fieldnames-and-propertynames/55364>\
**Category:** Internals & Design\
**Created:** [February 16, 2021, 12:25am UTC](https://discourse.julialang.org/t/slowness-of-fieldnames-and-propertynames/55364 "2021-02-16T00:25:16Z")\
**Posts on this page:** 1\
**Showing post:** 2

<div class="post-metadata">

**Author:** ![ericphanson](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/ericphanson/32/215186_2.png) [@ericphanson](https://discourse.julialang.org/u/ericphanson)\
**Post date:** [February 20, 2021, 2:56pm UTC](https://discourse.julialang.org/t/slowness-of-fieldnames-and-propertynames/55364/2 "2021-02-20T14:56:30Z")

</div>

I think this might be a related issue: I wanted to convert a `struct` into a `NamedTuple` (with the keys being the field names of the struct), and while it seems like the compiler has all the info it needs with

```julia
named_tuple(obj::T) where {T} = NamedTuple{fieldnames(T), Tuple{fieldtypes(T)...}}(ntuple(i -> getfield(obj, i), fieldcount(T)))

```

it is much slower than the equivalent generated function

```julia
@generated function named_tuple_gen(obj)
    NT = NamedTuple{fieldnames(obj), Tuple{fieldtypes(obj)...}}
    return :($NT(tuple($((:(getfield(obj, $i)) for i in 1:fieldcount(obj))...))))
end

```

which I got working by modifying [Static fieldnames - #2 by yuyichao](https://discourse.julialang.org/t/static-fieldnames/5619/2); thanks for the link! (I actually had to expand it out as

```julia
@generated function named_tuple_gen(obj::T) where {T}
    NT = NamedTuple{fieldnames(obj), Tuple{fieldtypes(obj)...}}
    return :(
                $NT(
                    tuple(
                        $(
                            (
                                :(getfield(obj, $i)) for i in 1:fieldcount(obj)
                            )...
                        )
                    )
                )
            )
end

```

to see what’s going on better; VSCode makes little vertical lines which makes it a bit easier to parse the paren alignment than here, though).

It seemed like I could recover some of the performance without generated functions if I wanted it just for a specific type `T` by doing

```julia
const NT = NamedTuple{fieldnames(T), Tuple{fieldtypes(T)...}}
T_as_NT(obj::T) = NT(ntuple(i -> getfield(obj, i), fieldcount(T)))

```

which at least lets it infer the output type correctly, though if there are more than 10 fields it can’t infer the output of `ntuple`. (It seems basically as fast as the generated function for fewer than 10 fields; the cutoff of course is from [julia/ntuple.jl at a319ae45a15fb005c7d7e29e3f3a62f27e18e3a6 · JuliaLang/julia · GitHub](https://github.com/JuliaLang/julia/blob/a319ae45a15fb005c7d7e29e3f3a62f27e18e3a6/base/ntuple.jl#L17-L32)).

> **Example with 11 fields**
>
> ```julia
> julia> using BenchmarkTools
> 
> julia> named_tuple(obj::T) where {T} = NamedTuple{fieldnames(T), Tuple{fieldtypes(T)...}}(ntuple(i -> getfield(obj, i), fieldcount(T)))
> named_tuple (generic function with 1 method)
> 
> julia> @generated function named_tuple_gen(obj::T) where {T}
> NT = NamedTuple{fieldnames(obj), Tuple{fieldtypes(obj)...}}
> return :($NT(tuple($((:(getfield(obj, $i)) for i in 1:fieldcount(obj))...))))
> end
> named_tuple_gen (generic function with 1 method)
> 
> julia> struct ManyBools
> x1::Bool
> x2::Bool
> x3::Bool
> x4::Bool
> x5::Bool
> x6::Bool
> x7::Bool
> x8::Bool
> x9::Bool
> x10::Bool
> x11::Bool
> end
> 
> julia> const NT = NamedTuple{fieldnames(ManyBools), Tuple{fieldtypes(ManyBools)...}}
> NamedTuple{(:x1, :x2, :x3, :x4, :x5, :x6, :x7, :x8, :x9, :x10, :x11), NTuple{11, Bool}}
> 
> julia> ManyBools_as_NT(obj::ManyBools) = NT(ntuple(i -> getfield(obj, i), fieldcount(ManyBools)))
> ManyBools_as_NT (generic function with 1 method)
> 
> julia> obj = ManyBools(rand(Bool, 11)...)
> ManyBools(false, false, false, false, false, true, false, true, false, false, true)
> 
> julia> @benchmark named_tuple($(Ref(obj))[])
> 
> BenchmarkTools.Trial: 
> memory estimate: 528 bytes
> allocs estimate: 6
> --------------
> minimum time: 2.768 μs (0.00% GC)
> median time: 2.806 μs (0.00% GC)
> mean time: 2.856 μs (0.75% GC)
> maximum time: 218.773 μs (98.07% GC)
> --------------
> samples: 10000
> evals/sample: 9
> 
> julia> @benchmark named_tuple_gen($(Ref(obj))[])
> BenchmarkTools.Trial: 
> memory estimate: 0 bytes
> allocs estimate: 0
> --------------
> minimum time: 2.125 ns (0.00% GC)
> median time: 2.250 ns (0.00% GC)
> mean time: 2.257 ns (0.00% GC)
> maximum time: 23.250 ns (0.00% GC)
> --------------
> samples: 10000
> evals/sample: 1000
> 
> julia> @benchmark ManyBools_as_NT($(Ref(obj))[])
> BenchmarkTools.Trial: 
> memory estimate: 128 bytes
> allocs estimate: 2
> --------------
> minimum time: 351.827 ns (0.00% GC)
> median time: 357.280 ns (0.00% GC)
> mean time: 368.931 ns (1.09% GC)
> maximum time: 9.198 μs (95.65% GC)
> --------------
> samples: 10000
> evals/sample: 214
> 
> julia> @code_warntype named_tuple(obj)
> Variables
> #self#::Core.Const(named_tuple)
> obj::ManyBools
> #5::var"#5#6"{ManyBools}
> 
> Body::NamedTuple
> 1 ─ %1 = Main.fieldnames($(Expr(:static_parameter, 1)))::Tuple{Vararg{Symbol, N} where N}
> │ %2 = Core.tuple(Main.Tuple)::Core.Const((Tuple,))
> │ %3 = Main.fieldtypes($(Expr(:static_parameter, 1)))::Tuple
> │ %4 = Core._apply_iterate(Base.iterate, Core.apply_type, %2, %3)::Type
> │ %5 = Core.apply_type(Main.NamedTuple, %1, %4)::Type{NamedTuple{_A, _B}} where {_A, _B}
> │ %6 = Main.:(var"#5#6")::Core.Const(var"#5#6")
> │ %7 = Core.typeof(obj)::Core.Const(ManyBools)
> │ %8 = Core.apply_type(%6, %7)::Core.Const(var"#5#6"{ManyBools})
> │ (#5 = %new(%8, obj))
> │ %10 = #5::var"#5#6"{ManyBools}
> │ %11 = Main.fieldcount($(Expr(:static_parameter, 1)))::Core.Const(11)
> │ %12 = Main.ntuple(%10, %11)::Tuple{Vararg{Bool, N} where N}
> │ %13 = (%5)(%12)::NamedTuple
> └── return %13
> 
> julia> @code_warntype named_tuple_gen(obj)
> Variables
> #self#::Core.Const(named_tuple_gen)
> obj::ManyBools
> 
> Body::NamedTuple{(:x1, :x2, :x3, :x4, :x5, :x6, :x7, :x8, :x9, :x10, :x11), NTuple{11, Bool}}
> 1 ─ %1 = Main.getfield(obj, 1)::Bool
> │ %2 = Main.getfield(obj, 2)::Bool
> │ %3 = Main.getfield(obj, 3)::Bool
> │ %4 = Main.getfield(obj, 4)::Bool
> │ %5 = Main.getfield(obj, 5)::Bool
> │ %6 = Main.getfield(obj, 6)::Bool
> │ %7 = Main.getfield(obj, 7)::Bool
> │ %8 = Main.getfield(obj, 8)::Bool
> │ %9 = Main.getfield(obj, 9)::Bool
> │ %10 = Main.getfield(obj, 10)::Bool
> │ %11 = Main.getfield(obj, 11)::Bool
> │ %12 = Main.tuple(%1, %2, %3, %4, %5, %6, %7, %8, %9, %10, %11)::NTuple{11, Bool}
> │ %13 = (NamedTuple{(:x1, :x2, :x3, :x4, :x5, :x6, :x7, :x8, :x9, :x10, :x11), NTuple{11, Bool}})(%12)::NamedTuple{(:x1, :x2, :x3, :x4, :x5, :x6, :x7, :x8, :x9, :x10, :x11), NTuple{11, Bool}}
> └── return %13
> 
> julia> @code_warntype ManyBools_as_NT(obj)
> Variables
> #self#::Core.Const(ManyBools_as_NT)
> obj::ManyBools
> #10::var"#10#11"{ManyBools}
> 
> Body::NamedTuple{(:x1, :x2, :x3, :x4, :x5, :x6, :x7, :x8, :x9, :x10, :x11), NTuple{11, Bool}}
> 1 ─ %1 = Main.:(var"#10#11")::Core.Const(var"#10#11")
> │ %2 = Core.typeof(obj)::Core.Const(ManyBools)
> │ %3 = Core.apply_type(%1, %2)::Core.Const(var"#10#11"{ManyBools})
> │ (#10 = %new(%3, obj))
> │ %5 = #10::var"#10#11"{ManyBools}
> │ %6 = Main.fieldcount(Main.ManyBools)::Core.Const(11)
> │ %7 = Main.ntuple(%5, %6)::Tuple{Vararg{Bool, N} where N}
> │ %8 = Main.NT(%7)::NamedTuple{(:x1, :x2, :x3, :x4, :x5, :x6, :x7, :x8, :x9, :x10, :x11), NTuple{11, Bool}}
> └── return %8
> 
> ```

> **Example with 9 fields**
>
> ```julia
> julia> using BenchmarkTools
> 
> julia> named_tuple(obj::T) where {T} = NamedTuple{fieldnames(T), Tuple{fieldtypes(T)...}}(ntuple(i -> getfield(obj, i), fieldcount(T)))
> named_tuple (generic function with 1 method)
> 
> julia> @generated function named_tuple_gen(obj::T) where {T}
> NT = NamedTuple{fieldnames(obj), Tuple{fieldtypes(obj)...}}
> return :($NT(tuple($((:(getfield(obj, $i)) for i in 1:fieldcount(obj))...))))
> end
> named_tuple_gen (generic function with 1 method)
> 
> julia> struct FewerBools
> x1::Bool
> x2::Bool
> x3::Bool
> x4::Bool
> x5::Bool
> x6::Bool
> x7::Bool
> x8::Bool
> x9::Bool
> end
> 
> julia> const NT = NamedTuple{fieldnames(FewerBools), Tuple{fieldtypes(FewerBools)...}}
> NamedTuple{(:x1, :x2, :x3, :x4, :x5, :x6, :x7, :x8, :x9), NTuple{9, Bool}}
> 
> julia> FewerBools_as_NT(obj::FewerBools) = NT(ntuple(i -> getfield(obj, i), fieldcount(FewerBools)))
> FewerBools_as_NT (generic function with 1 method)
> 
> julia> obj = FewerBools(rand(Bool, 9)...)
> FewerBools(false, true, false, true, true, true, false, true, false)
> 
> julia> @benchmark named_tuple($(Ref(obj))[])
> BenchmarkTools.Trial: 
> memory estimate: 384 bytes
> allocs estimate: 5
> --------------
> minimum time: 2.083 μs (0.00% GC)
> median time: 2.120 μs (0.00% GC)
> mean time: 2.152 μs (0.00% GC)
> maximum time: 4.940 μs (0.00% GC)
> --------------
> samples: 10000
> evals/sample: 9
> 
> julia> @benchmark named_tuple_gen($(Ref(obj))[])
> BenchmarkTools.Trial: 
> memory estimate: 0 bytes
> allocs estimate: 0
> --------------
> minimum time: 1.541 ns (0.00% GC)
> median time: 1.583 ns (0.00% GC)
> mean time: 1.590 ns (0.00% GC)
> maximum time: 5.791 ns (0.00% GC)
> --------------
> samples: 10000
> evals/sample: 1000
> 
> julia> @benchmark FewerBools_as_NT($(Ref(obj))[])
> BenchmarkTools.Trial: 
> memory estimate: 0 bytes
> allocs estimate: 0
> --------------
> minimum time: 1.833 ns (0.00% GC)
> median time: 1.917 ns (0.00% GC)
> mean time: 1.948 ns (0.00% GC)
> maximum time: 15.917 ns (0.00% GC)
> --------------
> samples: 10000
> evals/sample: 1000
> 
> julia> @code_warntype named_tuple(obj)
> Variables
> #self#::Core.Const(named_tuple)
> obj::FewerBools
> #5::var"#5#6"{FewerBools}
> 
> Body::NamedTuple
> 1 ─ %1 = Main.fieldnames($(Expr(:static_parameter, 1)))::Tuple{Vararg{Symbol, N} where N}
> │ %2 = Core.tuple(Main.Tuple)::Core.Const((Tuple,))
> │ %3 = Main.fieldtypes($(Expr(:static_parameter, 1)))::Tuple
> │ %4 = Core._apply_iterate(Base.iterate, Core.apply_type, %2, %3)::Type
> │ %5 = Core.apply_type(Main.NamedTuple, %1, %4)::Type{NamedTuple{_A, _B}} where {_A, _B}
> │ %6 = Main.:(var"#5#6")::Core.Const(var"#5#6")
> │ %7 = Core.typeof(obj)::Core.Const(FewerBools)
> │ %8 = Core.apply_type(%6, %7)::Core.Const(var"#5#6"{FewerBools})
> │ (#5 = %new(%8, obj))
> │ %10 = #5::var"#5#6"{FewerBools}
> │ %11 = Main.fieldcount($(Expr(:static_parameter, 1)))::Core.Const(9)
> │ %12 = Main.ntuple(%10, %11)::NTuple{9, Bool}
> │ %13 = (%5)(%12)::NamedTuple
> └── return %13
> 
> julia> @code_warntype named_tuple_gen(obj)
> Variables
> #self#::Core.Const(named_tuple_gen)
> obj::FewerBools
> 
> Body::NamedTuple{(:x1, :x2, :x3, :x4, :x5, :x6, :x7, :x8, :x9), NTuple{9, Bool}}
> 1 ─ %1 = Main.getfield(obj, 1)::Bool
> │ %2 = Main.getfield(obj, 2)::Bool
> │ %3 = Main.getfield(obj, 3)::Bool
> │ %4 = Main.getfield(obj, 4)::Bool
> │ %5 = Main.getfield(obj, 5)::Bool
> │ %6 = Main.getfield(obj, 6)::Bool
> │ %7 = Main.getfield(obj, 7)::Bool
> │ %8 = Main.getfield(obj, 8)::Bool
> │ %9 = Main.getfield(obj, 9)::Bool
> │ %10 = Main.tuple(%1, %2, %3, %4, %5, %6, %7, %8, %9)::NTuple{9, Bool}
> │ %11 = (NamedTuple{(:x1, :x2, :x3, :x4, :x5, :x6, :x7, :x8, :x9), NTuple{9, Bool}})(%10)::NamedTuple{(:x1, :x2, :x3, :x4, :x5, :x6, :x7, :x8, :x9), NTuple{9, Bool}}
> └── return %11
> 
> julia> @code_warntype FewerBools_as_NT(obj)
> Variables
> #self#::Core.Const(FewerBools_as_NT)
> obj::FewerBools
> #10::var"#10#11"{FewerBools}
> 
> Body::NamedTuple{(:x1, :x2, :x3, :x4, :x5, :x6, :x7, :x8, :x9), NTuple{9, Bool}}
> 1 ─ %1 = Main.:(var"#10#11")::Core.Const(var"#10#11")
> │ %2 = Core.typeof(obj)::Core.Const(FewerBools)
> │ %3 = Core.apply_type(%1, %2)::Core.Const(var"#10#11"{FewerBools})
> │ (#10 = %new(%3, obj))
> │ %5 = #10::var"#10#11"{FewerBools}
> │ %6 = Main.fieldcount(Main.FewerBools)::Core.Const(9)
> │ %7 = Main.ntuple(%5, %6)::NTuple{9, Bool}
> │ %8 = Main.NT(%7)::NamedTuple{(:x1, :x2, :x3, :x4, :x5, :x6, :x7, :x8, :x9), NTuple{9, Bool}}
> └── return %8
> 
> ```

---

_[View the full topic](https://discourse.julialang.org/t/slowness-of-fieldnames-and-propertynames/55364)._
