# Get NamedTuple element with String key. Why is this slow?

**URL:** <https://discourse.julialang.org/t/get-namedtuple-element-with-string-key-why-is-this-slow/78704>\
**Category:** Performance\
**Created:** [March 29, 2022, 11:00pm UTC](https://discourse.julialang.org/t/get-namedtuple-element-with-string-key-why-is-this-slow/78704 "2022-03-29T23:00:16Z")\
**Posts on this page:** 4\
**Page:** 1

<div class="post-metadata">

**Author:** ![jocklawrie](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/jocklawrie/32/33616_2.png) [@jocklawrie](https://discourse.julialang.org/u/jocklawrie)\
**Post date:** [March 29, 2022, 11:00pm UTC](https://discourse.julialang.org/t/get-namedtuple-element-with-string-key-why-is-this-slow/78704/1 "2022-03-29T23:00:16Z")

</div>

Hi there,

I’m attempting to get a value from a `NamedTuple` using a String key, ideally using `x[k]` syntax. In the example below, why is `x["a"]` so slow compared to `x[Symbol("a")]`?

```julia
using BenchmarkTools
import Base: getindex

getindex(x::NamedTuple, colname::String) = x[Symbol(colname)]

x = (a=1.1, b=2.2, c=3.3)

@benchmark $x[:a] # 1.4ns, native to Julia
@benchmark $x[Symbol("a")] # 1.5ns
@benchmark $x["a"] # 63ns

```

---

<div class="post-metadata">

**Author:** ![Henrique\_Becker](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/henrique_becker/32/15443_2.png) [@Henrique\_Becker](https://discourse.julialang.org/u/Henrique_Becker)\
**Post date:** [March 30, 2022, 1:12am UTC](https://discourse.julialang.org/t/get-namedtuple-element-with-string-key-why-is-this-slow/78704/2 "2022-03-30T01:12:36Z")

</div>

I think there is probably a problem with your benchmarking:

```julia
julia> using BenchmarkTools

julia> import Base: getindex

julia> getindex(x::NamedTuple, colname::String) = x[Symbol(colname)]
getindex (generic function with 207 methods)

julia> x = (a=1.1, b=2.2, c=3.3)
(a = 1.1, b = 2.2, c = 3.3)

julia> function test(x, s)
           ss = Symbol(s)
           display(@benchmark getindex($x, $ss))
           display(@benchmark getindex($x, Symbol($s)))
           display(@benchmark getindex($x, $s))
       end
test (generic function with 1 method)

julia> test(x, "a")
BenchmarkTools.Trial: 10000 samples with 996 evaluations.
 Range (min … max): 23.098 ns … 1.563 μs ┊ GC (min … max): 0.00% … 97.10%
 Time (median): 24.276 ns ┊ GC (median): 0.00%
 Time (mean ± σ): 26.685 ns ± 42.309 ns ┊ GC (mean ± σ): 4.97% ± 3.10%

  ▂▇██▇▅▂ ▁▁▁ ▂▂▃▂▁ ▂
  ███████▇▇▇▇████▇▅▆▆▄▄▅▆▄▆▇██████▆▇▆▇▇█▇▅▆▇▇▆▄▅▁▄▄▆▆▆▅▄▄▃▃▄▅ █
  23.1 ns Histogram: log(frequency) by time 43.4 ns <

 Memory estimate: 48 bytes, allocs estimate: 2.
BenchmarkTools.Trial: 10000 samples with 990 evaluations.
 Range (min … max): 43.611 ns … 1.448 μs ┊ GC (min … max): 0.00% … 95.98%
 Time (median): 45.656 ns ┊ GC (median): 0.00%
 Time (mean ± σ): 48.419 ns ± 43.207 ns ┊ GC (mean ± σ): 2.79% ± 3.04%

  ▁▅▇██▇▆▅▃ ▁▂▁▁ ▁▂▃▃▃▁ ▂
  ██████████▇██████▇▆▆▅▅▅▅███████▇▇███▇▅▆▆▆▄▃▅▅▅▆▅▄▅▃▁▃▄▅▅▅▅▅ █
  43.6 ns Histogram: log(frequency) by time 70.4 ns <

 Memory estimate: 48 bytes, allocs estimate: 2.
BenchmarkTools.Trial: 10000 samples with 990 evaluations.
 Range (min … max): 43.836 ns … 1.989 μs ┊ GC (min … max): 0.00% … 96.95%
 Time (median): 45.779 ns ┊ GC (median): 0.00%
 Time (mean ± σ): 48.997 ns ± 45.093 ns ┊ GC (mean ± σ): 2.85% ± 3.05%

  ▃██▇▆▃ ▁▁ ▁▁▂▂▁ ▂
  ██████▇████▇▆▆████████▇▆▆▆▅▅▄▆▆▄▃▂▃▄▅▄▅▅▆▄▄▂▄▃▂▃▃▄▄▆▅▄▃▅▄▄▄ █
  43.8 ns Histogram: log(frequency) by time 86.4 ns <

 Memory estimate: 48 bytes, allocs estimate: 2.

```

For me, the manual conversion and the new method for `getindex` are basically the same time, which is about the double of using a symbol directly. Note that my benchmarking probably avoids constant propagation, so the very fast results that you are seeing in your benchmark are probably the fact the compiler saw the `:a` was there, and then executed the computation during compilation.

---

<div class="post-metadata">

**Author:** ![Sukera](https://avatars.discourse-cdn.com/v4/letter/s/ce7236/32.png) [@Sukera](https://discourse.julialang.org/u/Sukera)\
**Post date:** [March 30, 2022, 5:17am UTC](https://discourse.julialang.org/t/get-namedtuple-element-with-string-key-why-is-this-slow/78704/3 "2022-03-30T05:17:57Z")

</div>

> [@jocklawrie](#):
>
> ```julia
> @benchmark $x[:a] # 1.4ns, native to Julia
> @benchmark $x[Symbol("a")] # 1.5ns
> @benchmark $x["a"] # 63ns
> 
> ```

Everything involved in these benchmarks, except for the last example, is fully inferrable by the compiler at compile time.

The first example is a literal symbol indexing into a “pasted in” literal named tuple - the compiler sees that only one specific element is needed and neither the tuple nor the key is used anywhere after that, so probably just returns the element in question instead.

The second example is the same, except that the compiler constant props the string through the `Symbol` constructor, again taking advantage of the fact that this kind of operation (creating symbols from literal string constants) is common and thus seeing the result at compile time.

The third example probably hits the only dynamic path - the compiler only knows that there’s a string, but not it’s content (which would require a symbol), so it has to fall back to the most generic version permitting strings.

* * *

This also explains why in the benchmark by @Henrique_Becker the second version has the same speed as the last version - the string is no longer a compile time constant and so the symbol has to be constructed at runtime.

---

<div class="post-metadata">

**Author:** ![jocklawrie](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/jocklawrie/32/33616_2.png) [@jocklawrie](https://discourse.julialang.org/u/jocklawrie)\
**Post date:** [March 30, 2022, 5:26am UTC](https://discourse.julialang.org/t/get-namedtuple-element-with-string-key-why-is-this-slow/78704/4 "2022-03-30T05:26:47Z")

</div>

Thanks both, that makes perfect sense.
