# 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:** 6\
**Page:** 1

<div class="post-metadata">

**Author:** ![Mason](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mason/32/2423_2.png) [@Mason](https://discourse.julialang.org/u/Mason)\
**Post date:** [February 16, 2021, 12:25am UTC](https://discourse.julialang.org/t/slowness-of-fieldnames-and-propertynames/55364/1 "2021-02-16T00:25:16Z")

</div>

So I’ve seen disucssions of this pop up a few times, but I figure it might be good to have a more recent thread here on discourse that we can point people to (and maybe it can motivate a PR / issue to julia)

The problem:

```julia
julia> struct Foo
           f1::Int
           f2::Int
           f3::Int
       end

julia> @btime fieldnames($Foo)
  396.512 ns (1 allocation: 32 bytes)
(:f1, :f2, :f3)

julia> @btime propertynames($(Foo(1, 2, 3)))
  394.821 ns (1 allocation: 32 bytes)
(:f1, :f2, :f3)

```

There are various usecases where one might want to reflect on these things without paying such a hefty performance penalty.

To my naive eye, it seems like at the very least the `fieldnames` is information that can be statically known and reflected on. It’s not like we are allowed to redefine the fieldnames:

```julia
julia> struct Foo
           f1::Int
           f2::Int
           some_other_fieldname::Int
       end
ERROR: invalid redefinition of constant Foo

```

In this thread: [Static fieldnames](https://discourse.julialang.org/t/static-fieldnames/5619), the OP was told to just use a generated function to manually hoist the reflection to compiletime, but that leaves me a little puzzled.

If it’s okay to reflect on this sort of thing inside a generated function body, why is it not okay for the compiler to just make this a `tfunc` or whatever and give us these results as compiletime constants?

* * *

Edit: I somehow missed [https://github.com/JuliaLang/julia/issues/29100](https://github.com/JuliaLang/julia/issues/29100) when I was searching around, so there is a _somewhat_ up to date discussion of this.

---

<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
> 
> ```

---

<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:** [March 10, 2021, 12:41am UTC](https://discourse.julialang.org/t/slowness-of-fieldnames-and-propertynames/55364/3 "2021-03-10T00:41:08Z")

</div>

I tried my example with 11 fields with [fix #29100, make `fieldnames` inferrable by JeffBezanson · Pull Request #39832 · JuliaLang/julia · GitHub](https://github.com/JuliaLang/julia/pull/39832); it seems like that PR did successfully make `fieldnames` infer (as claimed!), but the values still don’t get inferred:

```julia
julia> @code_warntype named_tuple(obj)
MethodInstance for named_tuple(::ManyBools)
  from named_tuple(obj::T) where T in Main at REPL[4]:1
Static Parameters
  T = ManyBools
Arguments
  #self#::Core.Const(named_tuple)
  obj::ManyBools
Locals
  #1::var"#1#2"{ManyBools}
Body::NamedTuple{(:x1, :x2, :x3, :x4, :x5, :x6, :x7, :x8, :x9, :x10, :x11), _A} where _A<:Tuple
1 ─ %1 = Main.fieldnames($(Expr(:static_parameter, 1)))::Core.Const((:x1, :x2, :x3, :x4, :x5, :x6, :x7, :x8, :x9, :x10, :x11))
│ %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{(:x1, :x2, :x3, :x4, :x5, :x6, :x7, :x8, :x9, :x10, :x11), _A}} where _A
│ %6 = Main.:(var"#1#2")::Core.Const(var"#1#2")
│ %7 = Core.typeof(obj)::Core.Const(ManyBools)
│ %8 = Core.apply_type(%6, %7)::Core.Const(var"#1#2"{ManyBools})
│ (#1 = %new(%8, obj))
│ %10 = #1::var"#1#2"{ManyBools}
│ %11 = Main.fieldcount($(Expr(:static_parameter, 1)))::Core.Const(11)
│ %12 = Main.ntuple(%10, %11)::Tuple{Vararg{Bool}}
│ %13 = (%5)(%12)::NamedTuple{(:x1, :x2, :x3, :x4, :x5, :x6, :x7, :x8, :x9, :x10, :x11), _A} where _A<:Tuple
└── return %13

```

I wonder if `Tuple{fieldtypes(T)...}` is just the wrong way to write this? Or maybe it’s a remaining inference issue with `fieldtypes`.

---

<div class="post-metadata">

**Author:** ![piever](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/piever/32/1815_2.png) [@piever](https://discourse.julialang.org/u/piever)\
**Post date:** [March 10, 2021, 9:56am UTC](https://discourse.julialang.org/t/slowness-of-fieldnames-and-propertynames/55364/4 "2021-03-10T09:56:34Z")

</div>

I’m also interested in this, because StructArrays uses something very similar (called `staticschema`) to turn a type into a named tuple type, eg

```julia
ComplexF64 -> NamedTuple{(:re, :im), Tuple{Float64, Float64}}

```

> [@ericphanson](#):
>
> I wonder if `Tuple{fieldtypes(T)...}` is just the wrong way to write this? Or maybe it’s a remaining inference issue with `fieldtypes` .

Have you tried a recursive approach? Maybe something like

```julia
julia> tupletype(::Tuple{}) = Tuple{}
tupletype (generic function with 1 method)

julia> function tupletype(t::Tuple)
           f, l = first(t), Base.tail(t)
           return Base.tuple_type_cons(f, tupletype(l))
       end
tupletype (generic function with 2 methods)

```

and then `tupletype(fieldtypes(T))`?

On a separate note (this does note fix the StructArrays usecase but may be helpful in your scenario), do you even need the tuple type? Couldn’t you just use

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

```

and let it infer the tuple type from the instance?

---

<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:** [March 10, 2021, 12:35pm UTC](https://discourse.julialang.org/t/slowness-of-fieldnames-and-propertynames/55364/5 "2021-03-10T12:35:48Z")

</div>

Great ideas!

> [@piever](#):
>
> `tupletype(fieldtypes(T))`

With this, I get

```julia
julia> named_tuple(obj::T) where {T} = NamedTuple{fieldnames(T), tupletype(fieldtypes(T))}(ntuple(i -> getfield(obj, i), fieldcount(T)))
named_tuple (generic function with 1 method)

julia> @code_warntype named_tuple(obj)
MethodInstance for named_tuple(::ManyBools)
  from named_tuple(obj::T) where T in Main at REPL[15]:1
Static Parameters
  T = ManyBools
Arguments
  #self#::Core.Const(named_tuple)
  obj::ManyBools
Locals
  #9::var"#9#10"{ManyBools}
Body::NamedTuple{(:x1, :x2, :x3, :x4, :x5, :x6, :x7, :x8, :x9, :x10, :x11), _A} where _A<:Tuple
1 ─ %1 = Main.fieldnames($(Expr(:static_parameter, 1)))::Core.Const((:x1, :x2, :x3, :x4, :x5, :x6, :x7, :x8, :x9, :x10, :x11))
│ %2 = Main.fieldtypes($(Expr(:static_parameter, 1)))::Tuple
│ %3 = Main.tupletype(%2)::Type
│ %4 = Core.apply_type(Main.NamedTuple, %1, %3)::Type{NamedTuple{(:x1, :x2, :x3, :x4, :x5, :x6, :x7, :x8, :x9, :x10, :x11), _A}} where _A
│ %5 = Main.:(var"#9#10")::Core.Const(var"#9#10")
│ %6 = Core.typeof(obj)::Core.Const(ManyBools)
│ %7 = Core.apply_type(%5, %6)::Core.Const(var"#9#10"{ManyBools})
│ (#9 = %new(%7, obj))
│ %9 = #9::var"#9#10"{ManyBools}
│ %10 = Main.fieldcount($(Expr(:static_parameter, 1)))::Core.Const(11)
│ %11 = Main.ntuple(%9, %10)::Tuple{Vararg{Bool}}
│ %12 = (%4)(%11)::NamedTuple{(:x1, :x2, :x3, :x4, :x5, :x6, :x7, :x8, :x9, :x10, :x11), _A} where _A<:Tuple
└── return %12

```

it looks about the same, unfortuantely.

> [@piever](#):
>
> Couldn’t you just use
> 
> ```julia
> named_tuple(obj::T) where {T} = NamedTuple{fieldnames(T)}(ntuple(i -> getfield(obj, i), fieldcount(T)))
> 
> ```
> 
> and let it infer the tuple type from the instance?

Good point! With that, I get

```julia
julia> named_tuple(obj::T) where {T} = NamedTuple{fieldnames(T)}(ntuple(i -> getfield(obj, i), fieldcount(T)))
named_tuple (generic function with 1 method)

julia> @code_warntype named_tuple(obj)
MethodInstance for named_tuple(::ManyBools)
  from named_tuple(obj::T) where T in Main at REPL[11]:1
Static Parameters
  T = ManyBools
Arguments
  #self#::Core.Const(named_tuple)
  obj::ManyBools
Locals
  #7::var"#7#8"{ManyBools}
Body::NamedTuple{(:x1, :x2, :x3, :x4, :x5, :x6, :x7, :x8, :x9, :x10, :x11), _A} where _A<:(Tuple{Vararg{Bool, N}} where N)
1 ─ %1 = Main.fieldnames($(Expr(:static_parameter, 1)))::Core.Const((:x1, :x2, :x3, :x4, :x5, :x6, :x7, :x8, :x9, :x10, :x11))
│ %2 = Core.apply_type(Main.NamedTuple, %1)::Core.Const(NamedTuple{(:x1, :x2, :x3, :x4, :x5, :x6, :x7, :x8, :x9, :x10, :x11), T} where T<:Tuple)
│ %3 = Main.:(var"#7#8")::Core.Const(var"#7#8")
│ %4 = Core.typeof(obj)::Core.Const(ManyBools)
│ %5 = Core.apply_type(%3, %4)::Core.Const(var"#7#8"{ManyBools})
│ (#7 = %new(%5, obj))
│ %7 = #7::var"#7#8"{ManyBools}
│ %8 = Main.fieldcount($(Expr(:static_parameter, 1)))::Core.Const(11)
│ %9 = Main.ntuple(%7, %8)::Tuple{Vararg{Bool}}
│ %10 = (%2)(%9)::NamedTuple{(:x1, :x2, :x3, :x4, :x5, :x6, :x7, :x8, :x9, :x10, :x11), _A} where _A<:(Tuple{Vararg{Bool, N}} where N)
└── return %10

```

so now it does infer that the values are `Bool`s, but not how many of them. I think that’s then an `ntuple` issue, so it seems like `fieldtypes` inference isn’t an issue here (since we don’t actually need that function, as you point out!).

At some point I wonder if a generated function is actually the right approach, because I suppose that’s the way to ask the compiler to do more calculations than it would otherwise at compile time.

---

<div class="post-metadata">

**Author:** ![simeonschaub](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/simeonschaub/32/216566_2.png) [@simeonschaub](https://discourse.julialang.org/u/simeonschaub)\
**Post date:** [March 10, 2021, 12:40pm UTC](https://discourse.julialang.org/t/slowness-of-fieldnames-and-propertynames/55364/6 "2021-03-10T12:40:03Z")

</div>

> [@ericphanson](#):
>
> At some point I wonder if a generated function is actually the right approach, because I suppose that’s the way to ask the compiler to do more calculations than it would otherwise at compile time.

Yes, that’s usually the way to go in situations like this.
