# Can the compiler hoist constant structure out of the loop?

**URL:** https://discourse.julialang.org/t/can-the-compiler-hoist-constant-structure-out-of-the-loop/135257
**Category:** New to Julia
**Tags:** question
**Created:** [January 25, 2026, 2:00pm UTC](https://discourse.julialang.org/t/can-the-compiler-hoist-constant-structure-out-of-the-loop/135257 "2026-01-25T14:00:54Z")
**Posts on this page:** 6
**Page:** 1

<div class="post-metadata">

### Author: ![WalterMadelim](https://avatars.discourse-cdn.com/v4/letter/w/3e96dc/32.png) [@WalterMadelim](https://discourse.julialang.org/u/WalterMadelim)
#### Post date: [January 25, 2026, 2:00pm UTC](https://discourse.julialang.org/t/can-the-compiler-hoist-constant-structure-out-of-the-loop/135257/1 "2026-01-25T14:00:54Z")

</div>

In practice I may want to write this function (only `r.x` is the object being considered, other parts are less relevant)

```julia-auto
function f!(r, I)
    for i=I
        r.x[i] = rand(Int)
    end
end

```

Assume now the structure `r.x` is fixed throughout. So a foolproof way to write this would be

```julia-auto
function g!(r, I)
    x = r.x # introduce an additional local name (I write `x` here)
    for i=I
        x[i] = rand(Int)
    end
end

```

But `g!` is lengthier. I wonder if julia’s compiler can directly perform the transformation to `g!` under the hood so I can just keep writing `f!` at the front end. Can I?

> **Test**
>
> Here is a simple test. But in practice my `r.x` may mean a field access from a NamedTuple `r` as well
> 
> ```julia-auto
> function test(N)
> r = Ref(rand(Int, N))
> I = Base.OneTo(N)
> @time g!(r, I)
> @time f!(r, I)
> @time g!(r, I)
> @time f!(r, I)
> end
> test(9999999)
> 
> ```

---

<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: [January 25, 2026, 2:26pm UTC](https://discourse.julialang.org/t/can-the-compiler-hoist-constant-structure-out-of-the-loop/135257/2 "2026-01-25T14:26:40Z")

</div>

The pedantic answer is “It depends on what `r` and `I` are.” Julia functions have unconstrained polymorphism, so without knowing what `r` and `I` are, this function could do literally anything.

That said, a somewhat safe assumption here would be that `r` is some form of possibly mutable struct containing a field `x` which has an array in it, and is using generic methods for `getproperty` and `setindex!`, and `I` is some well behaved iterable of integer indices. In this case, the answer is maybe, this is the sort of thing julia’s compiler is often good at hoisting out of loops, but there’s often some catches.

However, you may run into trouble when `r` is a mutable type, because then it’s really up to the compiler to decide if it is legal to hoist the pointer loading out of the loop. Your benchmark would appear to suggest that no, in this case the compiler decides not to perform the hoisting. You can check the `code_llvm` output of `f!` and `g!` to see for yourself what exactly it decides to do.

---

<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: [January 25, 2026, 8:37pm UTC](https://discourse.julialang.org/t/can-the-compiler-hoist-constant-structure-out-of-the-loop/135257/3 "2026-01-25T20:37:08Z")

</div>

> [@WalterMadelim](#):
>
> Assume now the structure `r.x` is fixed throughout.

Can we, or rather can the compiler? What’s stopping another thread with a reference to the same object assigned to `r` from reassigning the `x` property?

---

<div class="post-metadata">

### Author: ![WalterMadelim](https://avatars.discourse-cdn.com/v4/letter/w/3e96dc/32.png) [@WalterMadelim](https://discourse.julialang.org/u/WalterMadelim)
#### Post date: [January 26, 2026, 12:55am UTC](https://discourse.julialang.org/t/can-the-compiler-hoist-constant-structure-out-of-the-loop/135257/4 "2026-01-26T00:55:29Z")

</div>

Yes, if the field `x` is mutable, then the compiler cannot do hoisting.

But I found that it appears that I can get performance gain by manually do hoisting even if the `x` is immutable, e.g. `r = (x = [1,2],)`. I think in practice I have to bother doing manual hoisting

---

<div class="post-metadata">

### Author: ![WalterMadelim](https://avatars.discourse-cdn.com/v4/letter/w/3e96dc/32.png) [@WalterMadelim](https://discourse.julialang.org/u/WalterMadelim)
#### Post date: [January 26, 2026, 1:41am UTC](https://discourse.julialang.org/t/can-the-compiler-hoist-constant-structure-out-of-the-loop/135257/5 "2026-01-26T01:41:20Z")

</div>

> [@WalterMadelim](#):
>
> ```julia-auto
> function f!(r, I)
> for i=I
> r.x[i] = rand(Int)
> end
> end
> 
> ```

I think a more appropriate API design is

```julia-auto
_f!(r, I) = for i=I
    r[i] = rand(Int)
end;
f!(r, I) = _f!(r.x, I)

```

so my concern should be dispelled.

---

<div class="post-metadata">

### Author: ![foobar\_lv2](https://avatars.discourse-cdn.com/v4/letter/f/ee59a6/32.png) [@foobar\_lv2](https://discourse.julialang.org/u/foobar_lv2)
#### Post date: [January 26, 2026, 9:41am UTC](https://discourse.julialang.org/t/can-the-compiler-hoist-constant-structure-out-of-the-loop/135257/6 "2026-01-26T09:41:20Z")

</div>

> [@Benny](#):
>
> What’s stopping another thread with a reference to the same object assigned to `r` from reassigning the `x` property?

Nobody is stopping another thread from reassigning the `x` property. However, the compiler does not need to care, and this does not stop the compiler from hoisting the `x`:

If there is no atomic / acquire fence in the loop, and some other thread happens to reassign the `x` property… then this is a bad `undef`/`poison` race condition.

The compiler is at liberty to instead spawn “nasal bats”, i.e. do almost anything at all. (write goes to the updated `x` – correct program execution. Write goes to the old `x` due to hoisting – also correct program execution. Write goes into some internal datastructure, ransomware is downloaded and encrypts your hard-drive – also correct program execution)

> [@WalterMadelim](#):
>
> Yes, if the field `x` is mutable, then the compiler cannot do hoisting.

The compiler can hoist if it can prove that nothing inside the loop either updates `x` or is an atomic acquire.

This of course only happens if the loop body is pretty small – the compiler is not _that_ smart. Luckily, this hoisting only matters if the loop body is pretty small: If the load of `x` can amortize over a long and expensive loop body, then hoisting of the load is less than a rounding error in terms of performance.

The real thing to look out for are loop bodys that are typically small and fast, but contain very rarely executed code for edge-cases that do complicated stuff. For example, a 1 in a billion chance of having to write a debug log message (which interacts with io → needs a lock → is an acquire → no hoisting).

Theoretically LLVM has passes for that kind of thing (put the reload of `x` into the rare condition). But I found that not very reliable.
