# Allocations due to Boolean keyword arguments - how to avoid them?

**URL:** https://discourse.julialang.org/t/allocations-due-to-boolean-keyword-arguments-how-to-avoid-them/87654
**Category:** Performance
**Tags:** memory-allocation, val, boolean
**Created:** [September 22, 2022, 2:43pm UTC](https://discourse.julialang.org/t/allocations-due-to-boolean-keyword-arguments-how-to-avoid-them/87654 "2022-09-22T14:43:04Z")
**Posts on this page:** 10
**Page:** 1

<div class="post-metadata">

### Author: ![Krastanov](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/krastanov/32/6817_2.png) [@Krastanov](https://discourse.julialang.org/u/Krastanov)
#### Post date: [September 22, 2022, 2:43pm UTC](https://discourse.julialang.org/t/allocations-due-to-boolean-keyword-arguments-how-to-avoid-them/87654/1 "2022-09-22T14:43:05Z")

</div>

Deep in one of my projects I have a function that slightly changes behavior depending on a boolean keyword. It seems if the keyword is `Val{Bool}` there are no allocations but if it is `Bool`, then it allocates. Here is a contrived minimal example:

```julia
using BenchmarkTools

""" Some work object - it seems the example actually needs something as complicated """
struct Object{VecType<:AbstractVector{<:Unsigned}, MatType<:AbstractMatrix{<:Unsigned}}
    v::VecType
    m::MatType
end

""" Work function that happens to slightly change behavior depending on Val keyword """
function work_val(obj::Object; extrawork::Val{B}=Val(true)) where B
    r,c = size(obj.m)
    @inbounds for i in 1:r
        @inbounds for j in i+1:r
            for k in 1:c obj.m[i,k] ⊻= obj.m[j,k] end
            if B
                obj.v[i] ⊻= obj.m[i,j]
            end
        end
    end
end

"""A "public interface" function that uses a Bool instead of Val{Bool} """
function work_bool_to_val(obj::Object; extrawork::Bool=true)
    work_val(obj; extrawork=Val(extrawork))
end

n = 10
obj = Object(rand(UInt,n),rand(UInt,n,n))

```

The one that uses `Val` does not allocate.

```julia
@benchmark work_val($obj)
BenchmarkTools.Trial: 10000 samples with 244 evaluations.
 Range (min … max): 310.254 ns … 753.303 ns ┊ GC (min … max): 0.00% … 0.00%
 Memory estimate: 0 bytes, allocs estimate: 0.

```

_But the one that uses a `Bool` and then puts it inside of a `Val` does allocate even though it simply calls a non-allocating inner function._

```julia
@benchmark work_bool_to_val($obj)
BenchmarkTools.Trial: 10000 samples with 160 evaluations.
 Range (min … max): 668.881 ns … 15.615 μs ┊ GC (min … max): 0.00% … 93.26%
 Memory estimate: 32 bytes, allocs estimate: 1.

```

If I simply rewrite `work_val` to directly use `Bool` then things work fine. However, in the real case where I see this problem, such a rewrite is not possible. Thus my question is **If I can not modify `work_val`, what can I do in order to make `work_bool_to_val` not allocate?**.

This is not a question about refactoring and rethinking the structure of a code base, rather a very targeted question about **why a `Bool` keyword argument causes an allocation when the rest of the body of the function is not allocating**.

> **By the way, a minor simplification of the example makes everything non-allocating. This is deeply confusing to me. If \`work\_val\` never allocates, why do changes to it matter to whether \`work\_bool\_to\_val\` allocates? Click here to see this example.**
>
> ```julia
> using BenchmarkTools
> 
> """Just some contrived work function that happens to slightly change behavior depending on Val keyword"""
> function simple_work_val(obj::Vector; extrawork::Val{B}=Val(true)) where B
> l = size(obj,1)
> @inbounds for i in 1:l
> @inbounds for j in i+1:l
> obj[i] ⊻= obj[j]
> if B
> obj[i] += obj[i]
> end
> end
> end
> end
> 
> """A "public interface" function that uses a Bool keyword instead of a Val keyword"""
> function simple_work_bool_to_val(obj::Vector; extrawork::Bool=true)
> simple_work_val(obj; extrawork=Val(extrawork))
> end
> 
> n = 10
> obj = rand(UInt,n)
> @benchmark simple_work_val($obj)
> @benchmark simple_work_bool_to_val($obj)
> 
> ```
> 
> So, maybe it has something to do with the complicated `Object` type? Insight on this would be greatly appreciated.

> **Version Info: 1.8.0 (click to expand)**
>
> ```julia
> Julia Version 1.8.0
> Commit 5544a0fab76 (2022-08-17 13:38 UTC)
> Platform Info:
> OS: Linux (x86_64-linux-gnu)
> CPU: 16 × AMD Ryzen 7 1700 Eight-Core Processor
> WORD_SIZE: 64
> LIBM: libopenlibm
> LLVM: libLLVM-13.0.1 (ORCJIT, znver1)
> Threads: 1 on 16 virtual cores
> Environment:
> JULIA_EDITOR = code
> JULIA_NUM_THREADS = 1
> 
> ```

---

<div class="post-metadata">

### Author: ![lmiq](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/lmiq/32/18314_2.png) [@lmiq](https://discourse.julialang.org/u/lmiq)
#### Post date: [September 22, 2022, 2:54pm UTC](https://discourse.julialang.org/t/allocations-due-to-boolean-keyword-arguments-how-to-avoid-them/87654/2 "2022-09-22T14:54:56Z")

</div>

The allocation is probably occuring because by using the `Val` trick we are causing dynamic dispatch _on the call to_ to `simple_work_val` (not inside it). If you instead just use a boolean flag, the allocation disappears:

```julia
julia> function work_val(obj::Object; extrawork::Bool=true) 
           r,c = size(obj.m)
           @inbounds for i in 1:r
               @inbounds for j in i+1:r
                   for k in 1:c obj.m[i,k] ⊻= obj.m[j,k] end
                   if extrawork
                       obj.v[i] ⊻= obj.m[i,j]
                   end
               end
           end
       end
work_val (generic function with 1 method)

julia> @btime work_bool_to_val($obj)
  218.308 ns (0 allocations: 0 bytes)

```

---

<div class="post-metadata">

### Author: ![stevengj](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/stevengj/32/71_2.png) [@stevengj](https://discourse.julialang.org/u/stevengj)
#### Post date: [September 22, 2022, 2:58pm UTC](https://discourse.julialang.org/t/allocations-due-to-boolean-keyword-arguments-how-to-avoid-them/87654/3 "2022-09-22T14:58:26Z")

</div>

> [@Krastanov](#):
>
> `work_val(obj; extrawork=Val(extrawork))`

The problem is that this requires a runtime dynamic dispatch, since the compiler doesn’t know the type (it doesn’t know whether it is `Val{true}` or `Val{false}`) until runtime (unless you get luck with constant propagation, which I’m guessing doesn’t happen here).

I don’t see the point of `Val` in the code you quoted — you might as well use a `Bool` everywhere, since the `if B` is not in your innermost loop so it should have a negligible runtime cost.

---

<div class="post-metadata">

### Author: ![Krastanov](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/krastanov/32/6817_2.png) [@Krastanov](https://discourse.julialang.org/u/Krastanov)
#### Post date: [September 22, 2022, 3:37pm UTC](https://discourse.julialang.org/t/allocations-due-to-boolean-keyword-arguments-how-to-avoid-them/87654/4 "2022-09-22T15:37:15Z")

</div>

But then why is the second example working fine without allocations? Why is there no dynamic dispatch, even though it is the exact same flow of code?

I copy it here (it was in the `<details>` tag of the original post):

```julia
"""Just some contrived work function that happens to slightly change behavior depending on Val keyword"""
function simple_work_val(obj::Vector; extrawork::Val{B}=Val(true)) where B
    l = size(obj,1)
    @inbounds for i in 1:l
        @inbounds for j in i+1:l
            obj[i] ⊻= obj[j]
            if B
                obj[i] += obj[i]
            end
        end
    end
end

"""A "public interface" function that uses a Bool keyword instead of a Val keyword"""
function simple_work_bool_to_val(obj::Vector; extrawork::Bool=true)
    simple_work_val(obj; extrawork=Val(extrawork))
end

```

The reason for the `Val{true}` is that in the real non-minimal case it has noticeably different performance, presumably because it forces specialization. It is something I originally saw in [this thread](https://discourse.julialang.org/t/taking-ttfx-seriously-can-we-make-common-packages-faster-to-load-and-use/74949/15) with examples of its use in [SciML](https://github.com/SciML/OrdinaryDiffEq.jl/pull/1473/files#diff-8ce813ea8d7f370bc91b5ac1526a80f7fd354be769bafdd6b12b7368f3ae90a9L395) and [Polyester](https://github.com/JuliaSIMD/Polyester.jl/commit/5e6ae4c2ae009b507bbcf90ff2c2b9b7d5e94559#diff-a523f7f63af3c48f517501d7e392926093393f7d033fb208f477568ec30bec38L72). And here is a [real world example where simply switching from Bool to Val{Bool} eliminated allocations, because of this specialization issue](https://github.com/Krastanov/QuantumClifford.jl/commit/e8f94e95cc7b8d7a093a16f8022823daf5b8cb61) - the issue is that this example is rather long so I could not use it as a MWE.

---

<div class="post-metadata">

### Author: ![Krastanov](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/krastanov/32/6817_2.png) [@Krastanov](https://discourse.julialang.org/u/Krastanov)
#### Post date: [September 22, 2022, 3:47pm UTC](https://discourse.julialang.org/t/allocations-due-to-boolean-keyword-arguments-how-to-avoid-them/87654/5 "2022-09-22T15:47:16Z")

</div>

Oh, maybe I can answer my own question. Using `Base.@constprop :aggressive` makes the allocations go away in both cases.

Is there a standard way to say “I want aggressive constant propagation on this particular keyword argument”? Then presumably I would not need the `keyword::Val{Bool}=Val(true)` and could just use some `@constprop keyword::Bool=true`?

---

<div class="post-metadata">

### Author: ![lmiq](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/lmiq/32/18314_2.png) [@lmiq](https://discourse.julialang.org/u/lmiq)
#### Post date: [September 22, 2022, 4:32pm UTC](https://discourse.julialang.org/t/allocations-due-to-boolean-keyword-arguments-how-to-avoid-them/87654/6 "2022-09-22T16:32:38Z")

</div>

Does that really does any difference for performance in this case?

What could make sense is to specialize both the outer and the inner calls with `Val`, to completely eliminate a branch from the code. In this case, it would be:

```julia
julia> function work_bool_to_val(obj::Object; extrawork::Val{B}=Val(true)) where {B}
           work_val(obj; extrawork)
       end
work_bool_to_val (generic function with 1 method)

julia> function work_val(obj::Object; extrawork::Val{B}=Val(true)) where {B} 
           r,c = size(obj.m)
           @inbounds for i in 1:r
               @inbounds for j in i+1:r
                   for k in 1:c obj.m[i,k] ⊻= obj.m[j,k] end
                   if B
                       obj.v[i] ⊻= obj.m[i,j]
                   end
               end
           end
       end
work_val (generic function with 1 method)

julia> @btime work_bool_to_val($obj)
  258.361 ns (0 allocations: 0 bytes)

julia> @btime work_bool_to_val($obj; extrawork=Val(false))
  214.609 ns (0 allocations: 0 bytes)

```

I didn´t check lowered code, but I would expect that the branch is eliminated in this code when set to `false`. You could also define two independent versions, with and without the branch, and dispatch on them, to be sure that the branch would be eliminated in the non-extrawork case. The advantage of this approach would be that you could, for example, try to use `LoopVectorization` in some sense in the non-branched version and make it really much faster.

At the same time, compared to the function that simply has that branch, as they are, there is no perceivable performance difference, with the current codes.

---

<div class="post-metadata">

### Author: ![Elrod](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/elrod/32/22461_2.png) [@Elrod](https://discourse.julialang.org/u/Elrod)
#### Post date: [September 22, 2022, 4:48pm UTC](https://discourse.julialang.org/t/allocations-due-to-boolean-keyword-arguments-how-to-avoid-them/87654/7 "2022-09-22T16:48:37Z")

</div>

```julia
function work_bool_to_val(obj::Object; extrawork::Bool=true)
    if extrawork
        work_val(obj; extrawork=Val(true))
    else
        work_val(obj; extrawork=Val(false))
    end
end

```

---

<div class="post-metadata">

### Author: ![Krastanov](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/krastanov/32/6817_2.png) [@Krastanov](https://discourse.julialang.org/u/Krastanov)
#### Post date: [September 22, 2022, 5:11pm UTC](https://discourse.julialang.org/t/allocations-due-to-boolean-keyword-arguments-how-to-avoid-them/87654/8 "2022-09-22T17:11:34Z")

</div>

No, it does not make a difference in the toy example, as I warned in the first post. It makes a pretty substantial difference in the real-world case that I have linked to, but that one is much too big to share here.

---

<div class="post-metadata">

### Author: ![Krastanov](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/krastanov/32/6817_2.png) [@Krastanov](https://discourse.julialang.org/u/Krastanov)
#### Post date: [September 22, 2022, 5:13pm UTC](https://discourse.julialang.org/t/allocations-due-to-boolean-keyword-arguments-how-to-avoid-them/87654/9 "2022-09-22T17:13:15Z")

</div>

@Elrod, are you saying that the if-else branch with literal Val(true) and Val(false) basically lets the compiler to do the constant propagation? Is this just a fluke of which style lets constprop work with the current compiler, or is there something more fundamental here?

---

<div class="post-metadata">

### Author: ![lmiq](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/lmiq/32/18314_2.png) [@lmiq](https://discourse.julialang.org/u/lmiq)
#### Post date: [September 22, 2022, 6:00pm UTC](https://discourse.julialang.org/t/allocations-due-to-boolean-keyword-arguments-how-to-avoid-them/87654/10 "2022-09-22T18:00:08Z")

</div>

I think it is less fundamental 🤣 There you are just avoiding the dynamic dispatch that was causing the allocation.
