# Why is the NamedTuple slower? When/How would it be faster? Is it still allocated on the stack?

**URL:** https://discourse.julialang.org/t/why-is-the-namedtuple-slower-when-how-would-it-be-faster-is-it-still-allocated-on-the-stack/125902
**Category:** Performance
**Created:** [February 14, 2025, 12:51pm UTC](https://discourse.julialang.org/t/why-is-the-namedtuple-slower-when-how-would-it-be-faster-is-it-still-allocated-on-the-stack/125902 "2025-02-14T12:51:28Z")
**Posts on this page:** 7
**Page:** 1

<div class="post-metadata">

### Author: ![Vik1](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/vik1/32/35496_2.png) [@Vik1](https://discourse.julialang.org/u/Vik1)
#### Post date: [February 14, 2025, 12:51pm UTC](https://discourse.julialang.org/t/why-is-the-namedtuple-slower-when-how-would-it-be-faster-is-it-still-allocated-on-the-stack/125902/1 "2025-02-14T12:51:28Z")

</div>

In the following case, the Dict beats the NamedTuple in the creation and in the lookup? Why is this the case? Am I missing something?

```julia
using BenchmarkTools

tup1 = NamedTuple(k => v for (k, v) in [(:a, 5), (:b, 10)])
tup2 = Dict(k => v for (k, v) in [(:a, 5), (:b, 10)])

@btime tup1 = NamedTuple(k => v for (k, v) in [(:a, 5), (:b, 10)]) # -> 830.962 ns (13 allocations: 864 bytes)
@btime tup2 = Dict(k => v for (k, v) in [(:a, 5), (:b, 10)]) # -> 90.508 ns (6 allocations: 544 bytes)

@btime tup1.b # -> 31.407 ns (0 allocations: 0 bytes)
@btime tup2[:b] # -> 16.699 ns (0 allocations: 0 bytes)

```

I assume the tuple is still in the stack? When would it be faster?

---

<div class="post-metadata">

### Author: ![BdeKoning](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/bdekoning/32/214557_2.png) [@BdeKoning](https://discourse.julialang.org/u/BdeKoning)
#### Post date: [February 14, 2025, 1:20pm UTC](https://discourse.julialang.org/t/why-is-the-namedtuple-slower-when-how-would-it-be-faster-is-it-still-allocated-on-the-stack/125902/2 "2025-02-14T13:20:24Z")

</div>

For tuples the amount of elements is part of the type. So if you create a tuple for which the amount elements is not known at compile time (e.g. when you create a tuple from a vector), that will be slow. In the case of a NamedTuple the field names are also part of the types, and apparently they cannot be inferred at compile time here.

The dictionary is fine because it can be inferred that the source vector is of type `Vector{Tuple{Symbol, Int64}}` and thus that the dictionary is of type `Dict{Symbol, Int64}`.

---

<div class="post-metadata">

### Author: ![BdeKoning](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/bdekoning/32/214557_2.png) [@BdeKoning](https://discourse.julialang.org/u/BdeKoning)
#### Post date: [February 14, 2025, 1:23pm UTC](https://discourse.julialang.org/t/why-is-the-namedtuple-slower-when-how-would-it-be-faster-is-it-still-allocated-on-the-stack/125902/3 "2025-02-14T13:23:23Z")

</div>

I kind of expected `NamedTuple(k => v for (k, v) in ((:a, 5), (:b, 10)))` to be fast (i.e. generating the named tuple from a tuple of tuples), but it isn’t either.

---

<div class="post-metadata">

### Author: ![BdeKoning](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/bdekoning/32/214557_2.png) [@BdeKoning](https://discourse.julialang.org/u/BdeKoning)
#### Post date: [February 14, 2025, 1:25pm UTC](https://discourse.julialang.org/t/why-is-the-namedtuple-slower-when-how-would-it-be-faster-is-it-still-allocated-on-the-stack/125902/4 "2025-02-14T13:25:36Z")

</div>

Maybe someone can comment on the relationship between type instability and heap allocations?

---

<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 14, 2025, 1:35pm UTC](https://discourse.julialang.org/t/why-is-the-namedtuple-slower-when-how-would-it-be-faster-is-it-still-allocated-on-the-stack/125902/5 "2025-02-14T13:35:55Z")

</div>

> [@Vik1](#):
>
> ```julia
> @btime tup1 = NamedTuple(k => v for (k, v) in [(:a, 5), (:b, 10)]) # -> 830.962 ns (13 allocations: 864 bytes)
> @btime tup2 = Dict(k => v for (k, v) in [(:a, 5), (:b, 10)]) # -> 90.508 ns (6 allocations: 544 bytes)
> 
> ```

This is slow mostly because of the method being hit. If you had instead done

```julia-repl
julia> @btime NamedTuple{(:a, :b)}((5, 10))
  1.082 ns (0 allocations: 0 bytes)
(a = 5, b = 10)

```

you’d get it being “instant”, because in this case there’s literally nothing to do, the whole thing can be done at compile time. This only works in cases where you know the keys at compile time though (if you don’t, then `NamedTuple`s probably aren’t a good fit for your program).

> [@Vik1](#):
>
> ```julia
> @btime tup1.b # -> 31.407 ns (0 allocations: 0 bytes)
> @btime tup2[:b] # -> 16.699 ns (0 allocations: 0 bytes)
> 
> ```

This one mostly just comes down to you measuring untyped global variables. If you benchmark without the globals, you’ll see the named tuple is actually faster to access (when the key being accessed is known at compile time):

```julia-repl
julia> @btime tup.b setup=(tup = $tup1)
  1.943 ns (0 allocations: 0 bytes)
10

julia> @btime tup[:b] setup=(tup = $tup2)
  4.098 ns (0 allocations: 0 bytes)
10

```

---

<div class="post-metadata">

### Author: ![PatrickHaecker](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/patrickhaecker/32/222891_2.png) [@PatrickHaecker](https://discourse.julialang.org/u/PatrickHaecker)
#### Post date: [February 14, 2025, 1:39pm UTC](https://discourse.julialang.org/t/why-is-the-namedtuple-slower-when-how-would-it-be-faster-is-it-still-allocated-on-the-stack/125902/6 "2025-02-14T13:39:05Z")

</div>

> [@Vik1](#):
>
> ```julia
> tup1 = NamedTuple(k => v for (k, v) in [(:a, 5), (:b, 10)])
> tup2 = Dict(k => v for (k, v) in [(:a, 5), (:b, 10)])
> 
> ```

You are doing the benchmarking in global scope. If you want to get realistic values, you either need to move it in local scope or use `const` for `tup1` and `tup2`. Then accessing the `NamedTuple` is indeed faster.

I am not sure whether it helps, but the creation is fast when using this syntax `(a = 5, b = 10)`.

---

<div class="post-metadata">

### Author: ![Vik1](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/vik1/32/35496_2.png) [@Vik1](https://discourse.julialang.org/u/Vik1)
#### Post date: [February 14, 2025, 4:54pm UTC](https://discourse.julialang.org/t/why-is-the-namedtuple-slower-when-how-would-it-be-faster-is-it-still-allocated-on-the-stack/125902/7 "2025-02-14T16:54:48Z")

</div>

Thank you guys very much! I am sorry for benchmarking in the global scope, I should’ve realized. I switched the code in my actual use case as in the example and it’s a little faster 🙂 I am looking up around 50 times more often than creating, which is surely a big reason. But I also notice, that the script uses less RAM.  
I cannot benchmark reliably exactly, as I am working with a genetic algorithm. As for predefining the keys, I do not want to do that, as they are supposed to be user-definable (optimization objectives).
