# Why are tuple literals better for constant-propagation than array literals?

**URL:** <https://discourse.julialang.org/t/why-are-tuple-literals-better-for-constant-propagation-than-array-literals/69805>\
**Category:** Performance\
**Created:** [October 15, 2021, 6:35am UTC](https://discourse.julialang.org/t/why-are-tuple-literals-better-for-constant-propagation-than-array-literals/69805 "2021-10-15T06:35:11Z")\
**Posts on this page:** 13\
**Page:** 1

<div class="post-metadata">

**Author:** ![jules](https://avatars.discourse-cdn.com/v4/letter/j/41988e/32.png) [@jules](https://discourse.julialang.org/u/jules)\
**Post date:** [October 15, 2021, 6:35am UTC](https://discourse.julialang.org/t/why-are-tuple-literals-better-for-constant-propagation-than-array-literals/69805/1 "2021-10-15T06:35:11Z")

</div>

I need functions that check if a symbol references a specific type of struct field, and this is supposed to compile away through constant propagation.

Now, I also had this heuristic that I shouldn’t use huge Tuples because compilation time grows a lot with large Tuples, I’m not sure if that applies to homogenous ones, though. So I tested two definitions against each other, one with a Tuple and one with a Vector.

```julia
function test_vec(x)
    x in [:A,:B,:C,:D,:E,:F,:G,:H,:I,:J,:K,:L,:M,:N,:O,:P,:Q,:R,:S,:T,:U,:V,:W,:X,:Y,:Z,:a,:b,:c,:d,:e,:f,:g,:h,:i,:j,:k,:l,:m,:n,:o,:p,:q,:r,:s,:t,:u,:v,:w,:x,:y,:z]
end

function test_tuple(x)
    x in (:A,:B,:C,:D,:E,:F,:G,:H,:I,:J,:K,:L,:M,:N,:O,:P,:Q,:R,:S,:T,:U,:V,:W,:X,:Y,:Z,:a,:b,:c,:d,:e,:f,:g,:h,:i,:j,:k,:l,:m,:n,:o,:p,:q,:r,:s,:t,:u,:v,:w,:x,:y,:z)
end

```

The first look was at `@time`, just as a sanity check:

```julia
julia> @time test_tuple(:x)
  0.000000 seconds
true

julia> @time test_vec(:x)
  0.000003 seconds (1 allocation: 496 bytes)
true

```

So the vector version already allocates here, pointing to an “inefficient” use of the list of symbols.

Then I tested constant propagation:

```julia
function test_constprop_tuple()
    if test_tuple(:a)
        1
    else
        2
    end
end

julia> @code_native test_constprop_tuple()
        .section __TEXT,__ text,regular,pure_instructions
; ┌ @ Untitled-1:126 within `test_constprop_tuple'
        movl $1, %eax
        retq
        nopw %cs:(%rax,%rax)

```

The call is replaced by the constant `1`.

Now the vector version:

```julia
function test_constprop_vec()
    if test_vec(:a)
        1
    else
        2
    end
end

julia> @code_native test_constprop_vec()
        .section __TEXT,__ text,regular,pure_instructions
; ┌ @ Untitled-1:136 within `test_constprop_vec'
        pushq %rax
; │ @ Untitled-1:137 within `test_constprop_vec'
        movabsq $test_vec, %rax
        movabsq $4377851312, %rdi ## imm = 0x104F0B5B0
        callq *%rax
        andb $1, %al
        movzbl %al, %ecx
        movl $2, %eax
        subq %rcx, %rax
; │ @ Untitled-1:138 within `test_constprop_vec'
        popq %rcx
        retq
        nopw %cs:(%rax,%rax)

```

So my question is, why is an array literal not useable for constant propagation while a tuple literal is? Nothing can modify the array as it does not escape the function. Or is the compiler not sure that the only function applied to it, which is `in`, does not modify it?

---

<div class="post-metadata">

**Author:** ![JeffreySarnoff](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/jeffreysarnoff/32/1980_2.png) [@JeffreySarnoff](https://discourse.julialang.org/u/JeffreySarnoff)\
**Post date:** [October 15, 2021, 7:19am UTC](https://discourse.julialang.org/t/why-are-tuple-literals-better-for-constant-propagation-than-array-literals/69805/2 "2021-10-15T07:19:03Z")

</div>

Vectors are inherently mutable and Tuples are inherently immutable. This is built into Julia. So, even though you may never alter your Vector, the representation in memory is indirect (on the heap) and will support access that overwrites a current element. Tuple entries are not alterable, and this allows the representation in memory to be direct (faster access).

In short, tuple literals are constant and vector literals are not. That is the reason that tuples are better for constant propogation.

Very large Tuples are not generally used. As a guideline, once your tuple grows to ~32 elements some operations slow down, certainly beyond ~64 elements (depending upon the use) consider using a Vector. StaticArrays.jl, a widely used package, keeps its tuples \< 100 elements.

---

<div class="post-metadata">

**Author:** ![jules](https://avatars.discourse-cdn.com/v4/letter/j/41988e/32.png) [@jules](https://discourse.julialang.org/u/jules)\
**Post date:** [October 15, 2021, 7:55am UTC](https://discourse.julialang.org/t/why-are-tuple-literals-better-for-constant-propagation-than-array-literals/69805/3 "2021-10-15T07:55:49Z")

</div>

Yeah my only need is for looking up whether a field of a struct belongs to a special group or not. But the number of fields could be \>100. And it needs to work with constant propagation so that the check compiles away when fields are accessed in other functions.

---

<div class="post-metadata">

**Author:** ![JeffreySarnoff](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/jeffreysarnoff/32/1980_2.png) [@JeffreySarnoff](https://discourse.julialang.org/u/JeffreySarnoff)\
**Post date:** [October 15, 2021, 9:20am UTC](https://discourse.julialang.org/t/why-are-tuple-literals-better-for-constant-propagation-than-array-literals/69805/4 "2021-10-15T09:20:24Z")

</div>

I am not sure I follow your intent here. The ability to associate a field with a special group and the ability to access a field should not need to be intertwined. Are you expecting more than 100 fields for some particular struct, or is it that you expect a total of more than 100 fields over a collection of structs? Also, what type[s] of fields are expected? Is there one special group or are there several/many special groups?

---

<div class="post-metadata">

**Author:** ![jules](https://avatars.discourse-cdn.com/v4/letter/j/41988e/32.png) [@jules](https://discourse.julialang.org/u/jules)\
**Post date:** [October 15, 2021, 9:38am UTC](https://discourse.julialang.org/t/why-are-tuple-literals-better-for-constant-propagation-than-array-literals/69805/5 "2021-10-15T09:38:57Z")

</div>

Makie plot objects have attributes that are Observables. I’m trying to convert these to fields in a new design and an attribute field access needs different semantics than an ordinary field access. And yes one object can have upwards of 100 attribute fields.

---

<div class="post-metadata">

**Author:** ![JeffreySarnoff](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/jeffreysarnoff/32/1980_2.png) [@JeffreySarnoff](https://discourse.julialang.org/u/JeffreySarnoff)\
**Post date:** [October 15, 2021, 10:12am UTC](https://discourse.julialang.org/t/why-are-tuple-literals-better-for-constant-propagation-than-array-literals/69805/6 "2021-10-15T10:12:22Z")

</div>

Again, not sure this is on target for you.  
If you have n special groups where n is small, and for each of these groups you define a struct that contains the fields which belong to that special group, and you use an overarching struct which contains each of these substructs as immediate fields, and to keep the access uniform, define `getproperty` for each field so it can access subfields as defined. The `getproperty` variations can be defined in an small group of @eval loops.

---

<div class="post-metadata">

**Author:** ![rafael.guerra](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/rafael.guerra/32/216610_2.png) [@rafael.guerra](https://discourse.julialang.org/u/rafael.guerra)\
**Post date:** [October 15, 2021, 11:16am UTC](https://discourse.julialang.org/t/why-are-tuple-literals-better-for-constant-propagation-than-array-literals/69805/7 "2021-10-15T11:16:02Z")

</div>

> [@jules](#):
>
> ```julia
> [:A,:B,:C,:D,:E,:F,:G,:H,:I,:J,:K,:L,:M,:N,:O,:P,:Q,:R,:S,:T,:U,:V,:W,:X,:Y,:Z,:a,:b,:c,:d,:e,:f,:g,:h,:i,:j,:k,:l,:m,:n,:o,:p,:q,:r,:s,:t,:u,:v,:w,:x,:y,:z]
> 
> ```

Sorry for opening a parenthesis in this thread. Maybe the above could be written as:

```julia
Symbol.(union('a':'z','A':'Z'))

```

---

<div class="post-metadata">

**Author:** ![jules](https://avatars.discourse-cdn.com/v4/letter/j/41988e/32.png) [@jules](https://discourse.julialang.org/u/jules)\
**Post date:** [October 15, 2021, 11:58am UTC](https://discourse.julialang.org/t/why-are-tuple-literals-better-for-constant-propagation-than-array-literals/69805/8 "2021-10-15T11:58:00Z")

</div>

No I wanted a literal specifically to be sure to do everything I can for it to constant-prop. The real code will be macro-generated anyway. And it won’t be a list of letters 😉

---

<div class="post-metadata">

**Author:** ![GunnarFarneback](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/gunnarfarneback/32/1827_2.png) [@GunnarFarneback](https://discourse.julialang.org/u/GunnarFarneback)\
**Post date:** [October 15, 2021, 11:58am UTC](https://discourse.julialang.org/t/why-are-tuple-literals-better-for-constant-propagation-than-array-literals/69805/9 "2021-10-15T11:58:45Z")

</div>

> [@jules](#):
>
> I need functions that check if a symbol references a specific type of struct field, and this is supposed to compile away through constant propagation.

Sounds like something you could meta-program.

```julia
julia> macro make_test_fun(func, props)
           tests = Expr(:block)
           for prop in eval(props)
               push!(tests.args, :(x == $(Meta.quot(prop)) && return true))
           end
           return esc(quote
               function $func(x)
                   $tests
                   return false
               end
           end)
       end
@make_test_fun (macro with 1 method)

julia> @make_test_fun f [:x, :y, :z]
f (generic function with 1 method)

```

This gives you a function `f` equivalent to

```julia
function f(x)
    x == :x && return true
    x == :y && return true
    x == :z && return true
    return false
end

```

which should allow constant propagation.

---

<div class="post-metadata">

**Author:** ![jules](https://avatars.discourse-cdn.com/v4/letter/j/41988e/32.png) [@jules](https://discourse.julialang.org/u/jules)\
**Post date:** [October 15, 2021, 12:00pm UTC](https://discourse.julialang.org/t/why-are-tuple-literals-better-for-constant-propagation-than-array-literals/69805/10 "2021-10-15T12:00:20Z")

</div>

This thread drifts a bit off target, I already have a way to program this in a constant-propagating way (the if block would also suffice, yes). I merely wanted to know why the compiler can’t/doesn’t constant-propagate with an array of literal values that is not modified. I know about the differences between tuples and arrays, too, but not how the compiler deals with specifically written out collections like in my case.

---

<div class="post-metadata">

**Author:** ![GunnarFarneback](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/gunnarfarneback/32/1827_2.png) [@GunnarFarneback](https://discourse.julialang.org/u/GunnarFarneback)\
**Post date:** [October 15, 2021, 12:06pm UTC](https://discourse.julialang.org/t/why-are-tuple-literals-better-for-constant-propagation-than-array-literals/69805/11 "2021-10-15T12:06:37Z")

</div>

Someone who really knows should confirm but from what I’ve read elsewhere constant propagation could work in the vector case too if Julia’s compiler had a better escape analysis than it currently has. For the immutable case of a tuple there’s no need to do any escape analysis.

---

<div class="post-metadata">

**Author:** ![anon56330260](https://avatars.discourse-cdn.com/v4/letter/a/f07891/32.png) [@anon56330260](https://discourse.julialang.org/u/anon56330260)\
**Post date:** [October 15, 2021, 2:25pm UTC](https://discourse.julialang.org/t/why-are-tuple-literals-better-for-constant-propagation-than-array-literals/69805/12 "2021-10-15T14:25:09Z")

</div>

I think currently Julia doesn’t perform any special optimization for `Array` at Julia IR level. It directly lower `Array` construction to a ccall to Julia’s runtime and none of the high level information is used, and LLVM knows nothing special about this function call. So it even fails to optimize this function to a non-op:

```julia
function f()
    x = [1,2,3]
    return nothing
end

```

Anyway, doing this kind of optimization (constant propagation for mutable) needs to perform alias analysis on Julia’s IR, which is expensive. C++ doesn’t suffer from this because `std::vector` is implemented in C++, so compiler can see every information in `std::vector`, and they can just focus on the optimization of a low level form IR (LLVM IR). If you check the LLVM IR of the following code:

```julia
function f()
    x = [1,2,3]
    return x[1]
end

```

You can see that LLVM can figure out the return value is `1`. But it can’t remove the call to array allocation (since it doesn’t know this is an allocation).

I guess we just need to teach LLVM some information of Julia’s Array and we can get the optimization for free…

---

<div class="post-metadata">

**Author:** ![gbaraldi](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/gbaraldi/32/22101_2.png) [@gbaraldi](https://discourse.julialang.org/u/gbaraldi)\
**Post date:** [October 15, 2021, 2:35pm UTC](https://discourse.julialang.org/t/why-are-tuple-literals-better-for-constant-propagation-than-array-literals/69805/13 "2021-10-15T14:35:13Z")

</div>

This is what is currently been looked at [https://github.com/JuliaLang/julia/pull/41777](https://github.com/JuliaLang/julia/pull/41777)
