# How to avoid allocations when reading arbitrary keys from a NamedTuple?

**URL:** https://discourse.julialang.org/t/how-to-avoid-allocations-when-reading-arbitrary-keys-from-a-namedtuple/71806
**Category:** Performance
**Tags:** memory-allocation, namedtuple, type-stability
**Created:** [November 20, 2021, 12:43am UTC](https://discourse.julialang.org/t/how-to-avoid-allocations-when-reading-arbitrary-keys-from-a-namedtuple/71806 "2021-11-20T00:43:08Z")
**Posts on this page:** 9
**Page:** 1

<div class="post-metadata">

### Author: ![mattwigway](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mattwigway/32/22520_2.png) [@mattwigway](https://discourse.julialang.org/u/mattwigway)
#### Post date: [November 20, 2021, 12:43am UTC](https://discourse.julialang.org/t/how-to-avoid-allocations-when-reading-arbitrary-keys-from-a-namedtuple/71806/1 "2021-11-20T00:43:08Z")

</div>

I have a situation where I’m processing rowwise data from a JuliaDB table in a reducer. I’m having trouble with type stability, because my function looks like:

```julia
function (row, col)
   value = row[col]
   ...do things with value...
end

```

This is not surprising because Julia doesn’t know at compile time which column y will be and, and therefore what type it is, and therefore it is unstable and causing allocations. But I know that `x[y]` is an Int64, so I tried annotating the type:

```julia
function (row, col)
   value::Int64 = row[col]::Int64
   ...do things with value...
end

```

but it is still unstable. How can I annotate `row[col]` to avoid allocations?

Here’s a MWE (if you enclose the whole thing in a function, the allocations go away for both functions, but only because Julia can infer the value and type of t at compile time - which doesn’t solve my overall problem):

```julia
t = (abc=1, tba=2)

unstable(x, i) = x[i]
annotated(x, i) = x[i]::Int64

# run once to compile
unstable(t, :abc)
annotated(t, :abc)

println("no type annotation, allocation expected")
@time unstable(t, :abc) # -> 0.000018 seconds (1 allocation: 32 bytes)

println("type annotation, no allocation expected")
@time annotated(t, :abc) # -> 0.000004 seconds (1 allocation: 32 bytes)

```

---

<div class="post-metadata">

### Author: ![goerch](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/goerch/32/29122_2.png) [@goerch](https://discourse.julialang.org/u/goerch)
#### Post date: [November 20, 2021, 12:59am UTC](https://discourse.julialang.org/t/how-to-avoid-allocations-when-reading-arbitrary-keys-from-a-namedtuple/71806/2 "2021-11-20T00:59:20Z")

</div>

> [@mattwigway](#):
>
> (if you enclose the whole thing in a function, the allocations go away for both functions, but only because Julia can infer the value and type of t at compile time - which doesn’t solve my overall problem)

That sounds suspicious to me, because it would be the first thing to try. Better MWE then?

---

<div class="post-metadata">

### Author: ![mattwigway](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mattwigway/32/22520_2.png) [@mattwigway](https://discourse.julialang.org/u/mattwigway)
#### Post date: [November 20, 2021, 2:36am UTC](https://discourse.julialang.org/t/how-to-avoid-allocations-when-reading-arbitrary-keys-from-a-namedtuple/71806/3 "2021-11-20T02:36:10Z")

</div>

You’re right, that was a bad example. Here’s a better one. The critical thing is that which key is to be extracted from the NamedTuple is not known at compile time, and that the NamedTuple has values that are not all the same type. Here’s a better example, with the key read from the command-line arguments:

```julia

unstable(x, i) = x[i]
annotated(x, i) = x[i]::Int64

function main()
    t = (abc=1, tba=2.0, cd="jgds")

    # run once to compile
    unstable(t, :abc)
    annotated(t, :abc)

    ref = Symbol(ARGS[1])

    println("no type annotation, allocation expected")
    @time unstable(t, ref)

    println("type annotation, no allocation expected")
    @time annotated(t, ref)

end

main()

```

Running `julia namedtuple_test.jl abc` results in:

```julia
no type annotation, allocation expected
  0.000001 seconds (1 allocation: 32 bytes)
type annotation, no allocation expected
  0.000001 seconds (1 allocation: 32 bytes)

```

The first allocation is expected, because Julia has no way to know what will come out of the NamedTuple, and not all fields are bitstypes, so it is type-unstable. In the second one, I explicitly tell Julia that it’s going to be an Int64, so I would expect there to not be any allocations in that case, but there still is one.

---

<div class="post-metadata">

### Author: ![jling](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/jling/32/212909_2.png) [@jling](https://discourse.julialang.org/u/jling)
#### Post date: [November 20, 2021, 2:39am UTC](https://discourse.julialang.org/t/how-to-avoid-allocations-when-reading-arbitrary-keys-from-a-namedtuple/71806/4 "2021-11-20T02:39:38Z")

</div>

> [@mattwigway](#):
>
> I explicitly tell Julia that it’s going to be an Int64

it’s just a type assertion, and as you’ve seen, the allocation happens before the assertion (during the `getindex`).

 ![image](https://global.discourse-cdn.com/julialang/original/3X/c/e/cee667fe65550dced27069127afc1ddd970972d1.png)

---

<div class="post-metadata">

### Author: ![goerch](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/goerch/32/29122_2.png) [@goerch](https://discourse.julialang.org/u/goerch)
#### Post date: [November 20, 2021, 11:15am UTC](https://discourse.julialang.org/t/how-to-avoid-allocations-when-reading-arbitrary-keys-from-a-namedtuple/71806/5 "2021-11-20T11:15:30Z")

</div>

Maybe this helps:

```julia
using BenchmarkTools

access(x, i) = x[i]

function test1(symbol)
    t = (abc=1, tba=2.0, cd="jgds")

    access(t, symbol)
end

dispatch(::Val{:abc}, x) = access(x, :abc)
dispatch(::Val{:tba}, x) = access(x, :tab)
dispatch(::Val{:tba}, x) = access(x, :cd)

function test2(symbol)
    t = (abc=1, tba=2.0, cd="jgds")

    dispatch(Val(symbol), t)
end

symbol = :abc
@btime test1(symbol)
@btime test2(symbol)

```

with

```julia
  36.657 ns (1 allocation: 32 bytes)
  184.203 ns (0 allocations: 0 bytes)

```

---

<div class="post-metadata">

### Author: ![CameronBieganek](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/cameronbieganek/32/6915_2.png) [@CameronBieganek](https://discourse.julialang.org/u/CameronBieganek)
#### Post date: [November 20, 2021, 6:42pm UTC](https://discourse.julialang.org/t/how-to-avoid-allocations-when-reading-arbitrary-keys-from-a-namedtuple/71806/6 "2021-11-20T18:42:55Z")

</div>

I think the allocation that you observe with `unstable` is a benchmarking artifact. If you use `BenchmarkTools`, it goes away:

```julia
using BenchmarkTools
t = (abc=1, tba=2.0)
unstable(x, i) = x[i]

```

```julia
julia> @btime unstable($t, :abc);
  0.039 ns (0 allocations: 0 bytes)

```

Of course now we see another benchmarking artifact, which is the sub-nanosecond runtime. We can fix that with the [`Ref` trick](https://juliaci.github.io/BenchmarkTools.jl/stable/manual/#Understanding-compiler-optimizations):

```julia
julia> @btime unstable($(Ref(t))[], :abc);
  1.472 ns (0 allocations: 0 bytes)

```

As long as you’re using `unstable` in a local scope with local variables, then it should be type-stable.

---

<div class="post-metadata">

### Author: ![rocco\_sprmnt21](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/rocco_sprmnt21/32/20127_2.png) [@rocco\_sprmnt21](https://discourse.julialang.org/u/rocco_sprmnt21)
#### Post date: [May 24, 2022, 3:05pm UTC](https://discourse.julialang.org/t/how-to-avoid-allocations-when-reading-arbitrary-keys-from-a-namedtuple/71806/7 "2022-05-24T15:05:58Z")

</div>

> [@mattwigway](#):
>
> the NamedTuple has values that are not all the same type.

I intervene to ask a question that makes me understand something about how these mechanisms work, rather than suggesting some kind of solution.  
If your situation allows it, wouldn’t it be useful to change and use col [row] instead of working on row [col]?

---

<div class="post-metadata">

### Author: ![junder873](https://avatars.discourse-cdn.com/v4/letter/j/e95f7d/32.png) [@junder873](https://discourse.julialang.org/u/junder873)
#### Post date: [September 2, 2022, 7:28pm UTC](https://discourse.julialang.org/t/how-to-avoid-allocations-when-reading-arbitrary-keys-from-a-namedtuple/71806/8 "2022-09-02T19:28:19Z")

</div>

I am running into what I think is the same underlying issue (though possibly not, I am happy to start a different thread if that is appropriate).

When I try to access an arbitrary value in a NamedTuple, I see some allocations if the value is not defined within the function. For example:

```julia
using BenchmarkTools
nt = NamedTuple{(:a, :b, :c, :d)}((1, 2, 3, 4))
@btime $nt[:a];
# 1.900 ns (0 allocations: 0 bytes)
n = :a
@btime $nt[$n]
# 19.520 ns (1 allocation: 48 bytes)

```

This does not seem to be an effect of benchmarking since if I do something like

```julia
function test(nt, n)
    for i in 1:1_000_000
        nt[n]
    end
end

@time test(nt, :a)
# 0.030643 seconds (1.00 M allocations: 45.977 MiB, 5.38% gc time, 14.04% compilation time)

```

Which seems to suggest the problem exists there too (if I replace `nt[n]` with `nt[:a]` it does not allocate much.

What am I doing wrong?

---

<div class="post-metadata">

### Author: ![Benny](https://avatars.discourse-cdn.com/v4/letter/b/49beb7/32.png) [@Benny](https://discourse.julialang.org/u/Benny)
#### Post date: [September 2, 2022, 9:00pm UTC](https://discourse.julialang.org/t/how-to-avoid-allocations-when-reading-arbitrary-keys-from-a-namedtuple/71806/9 "2022-09-02T21:00:01Z")

</div>

You’re not doing anything wrong, in fact you wrote a test perfectly avoiding global variable artifacts on your first try. And yes, you’re on the right thread.

Julia generally specializes a call on the types of the called object and the arguments. For example, `f(1, 1.5)` specializes on `(typeof(f), Int64, Float64)`. By this logic, `getindex`/`getfield` would be _unstable_ for `Tuple`/`NamedTuple`/`struct` because the element types are heterogeneous! For example, `(1, 1.5)[1]` is an `Int64`, yet `(1, 1.5)[2]` is a `Float64`, a clear type instability. So, the compiler goes the extra mile to maintain performance for these important methods: if the index or field is a constant at compile-time, the compiler will “specialize” on the index/field’s _instance_. The compiler also does constant propagation for other function calls, but usually to evaluate the call at compile-time when all arguments are constant; instead, I’m talking about when `getindex`/`getfield` happens at runtime but the index/field is fixed for type stability.

```julia
julia> let # local scope, all variables are local
    t = (abc=1, tba=2.0, cd="jgds")
    i = :tba # recognized as constant in scope
    @time t[i]
  end
  0.000000 seconds
2.0

```

If you only take the examples in this thread without global variable artifacts, you’ll notice that whenever the field `:abc` is a constant in a local scope, there’s no allocation. However, whenever the field is a function argument `i` or command line argument `ARGS[1]`, the compiler has to treat it as a variable in the scope and may allocate to handle the type instability.
