# Help to understand the performance gain when using Base.throw\_boundscheck

**URL:** https://discourse.julialang.org/t/help-to-understand-the-performance-gain-when-using-base-throw-boundscheck/132801
**Category:** General Usage
**Created:** [October 1, 2025, 10:20am UTC](https://discourse.julialang.org/t/help-to-understand-the-performance-gain-when-using-base-throw-boundscheck/132801 "2025-10-01T10:20:40Z")
**Posts on this page:** 14
**Page:** 1

<div class="post-metadata">

### Author: ![Ronis\_BR](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/ronis_br/32/50999_2.png) [@Ronis\_BR](https://discourse.julialang.org/u/Ronis_BR)
#### Post date: [October 1, 2025, 10:20am UTC](https://discourse.julialang.org/t/help-to-understand-the-performance-gain-when-using-base-throw-boundscheck/132801/1 "2025-10-01T10:20:40Z")

</div>

Hi! I want to understand a strange behavior for me. We are building a lower triangular storage for gravity models. If I use the following `getindex`:

```julia
function Base.getindex(
    L::LowerTriangularStorage{T},
    i::Int,
    j::Int
) where T<:AbstractIcgemCoefficient
    @inline
    @boundscheck (i > L.n || j > L.n || i < 1 || j < 1) && throw(BoundsError(L, (i, j)))

    # For the upper triangular part, return zero.
    j > i && return zero(T)

    return L.data[_ij_to_lt_index(i, j)]
end

```

I hit a major performance hit compared to a usual matrix. In this case, it takes 30% more time to fetch the element (the values are very low, 2.5 ns vs 3.3 ns, but we need to fetch **a lot** of coefficients). However, if I change to:

```julia
function Base.getindex(
    L::LowerTriangularStorage{T},
    i::Int,
    j::Int
) where T<:AbstractIcgemCoefficient
    @inline
    @boundscheck (i > L.n || j > L.n || i < 1 || j < 1) && Base.throw_boundserror(L, (i, j))

    # For the upper triangular part, return zero.
    j > i && return zero(T)

    return L.data[_ij_to_lt_index(i, j)]
end

```

Everything is fine! I think it might be something related to throw\_boundserror marked as @noinline but I have no ideia. Can anyone explain to me please?

---

<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: [October 1, 2025, 10:27am UTC](https://discourse.julialang.org/t/help-to-understand-the-performance-gain-when-using-base-throw-boundscheck/132801/2 "2025-10-01T10:27:57Z")

</div>

Maybe instruction cache misses? How does `@boundscheck (i > L.n || j > L.n || i < 1 || j < 1) && @noinline throw(BoundsError(L, (i, j)))` do relative to these 2?

---

<div class="post-metadata">

### Author: ![Ronis\_BR](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/ronis_br/32/50999_2.png) [@Ronis\_BR](https://discourse.julialang.org/u/Ronis_BR)
#### Post date: [October 1, 2025, 10:49am UTC](https://discourse.julialang.org/t/help-to-understand-the-performance-gain-when-using-base-throw-boundscheck/132801/3 "2025-10-01T10:49:08Z")

</div>

This version also has a bad performance (equal to the version without the `@noinline`).

---

<div class="post-metadata">

### Author: ![Ronis\_BR](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/ronis_br/32/50999_2.png) [@Ronis\_BR](https://discourse.julialang.org/u/Ronis_BR)
#### Post date: [October 1, 2025, 10:52am UTC](https://discourse.julialang.org/t/help-to-understand-the-performance-gain-when-using-base-throw-boundscheck/132801/4 "2025-10-01T10:52:12Z")

</div>

However, creating and calling a lambda function leads to the good performance:

```julia
    @boundscheck (i > L.n || j > L.n || i < 1 || j < 1) &&
        (() -> (@noinline; throw(BoundsError(L, (i, j)))))()

```

---

<div class="post-metadata">

### Author: ![matthias314](https://avatars.discourse-cdn.com/v4/letter/m/a88e4f/32.png) [@matthias314](https://discourse.julialang.org/u/matthias314)
#### Post date: [October 1, 2025, 12:18pm UTC](https://discourse.julialang.org/t/help-to-understand-the-performance-gain-when-using-base-throw-boundscheck/132801/5 "2025-10-01T12:18:43Z")

</div>

This seems to be relevant;

> <https://github.com/JuliaLang/julia/issues/41307>
>
> I have noticed that the run-time of a function may significantly depend on wheth…er the function body contains \`throw\`, even if no exception is thrown. The performance tips in the manual don't mention this. I therefore wonder whether it is a bug. The following MWE is adapted from the method \`typed\_hvcat(::Type{T}, rows::Tuple{Vararg{Int}}, xs::Number...) where T\` in \`abstractarray.jl\`.
> \`\`\`
> @noinline throw\_arg\_err(msg) = throw(ArgumentError(msg))
> 
> function hv\_cat(rows::Tuple{Vararg{Int}}, xs::Int...)
> nr = length(rows)
> nc = rows\[1\]
> for i = 2:nr
> if nc != rows\[i\]
> throw(ArgumentError("mismatch")) # SLOW
> # throw\_arg\_err("mismatch") # FAST
> end
> end
> # Base.hvcat\_fill(Matrix{Int}(undef, nr, nc), xs) # v1.6.1
> Base.hvcat\_fill!(Matrix{Int}(undef, nr, nc), xs) # master
> end
> \`\`\`
> The SLOW variant gives
> \`\`\`
> julia\> @btime hv\_cat((1,1,1), 1,2,3);
> 107.800 ns (2 allocations: 144 bytes)
> \`\`\`
> The FAST variant is 3x faster (and makes fewer allocations):
> \`\`\`
> julia\> @btime hv\_cat((1,1,1), 1,2,3);
> 34.530 ns (1 allocation: 112 bytes)
> \`\`\`
> Note that the code differs only in a branch that is not executed. I've tried to replace the call to \`Base.hvcat\_fill!\` at the end with some other code, but then the difference disappeared. It also disappears if one changes the arguments to \`hv\_cat((1,1), 1,2)\`.
> \`\`\`
> Julia Version 1.8.0-DEV.61
> Commit 7553ca13cc (2021-06-21 17:18 UTC)
> Platform Info:
> OS: Linux (x86\_64-linux-gnu)
> CPU: Intel(R) Core(TM) i3-10110U CPU @ 2.10GHz
> WORD\_SIZE: 64
> LIBM: libopenlibm
> LLVM: libLLVM-12.0.0 (ORCJIT, skylake)
> \`\`\`
> The example also works for Julia 1.6.1 with the slight change indicated in the code.

---

<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: [October 1, 2025, 12:26pm UTC](https://discourse.julialang.org/t/help-to-understand-the-performance-gain-when-using-base-throw-boundscheck/132801/6 "2025-10-01T12:26:38Z")

</div>

Yeah, this is a known effect that crops up all the time. In any sort of small, performance sensitive function, it’s often greatly advantageous to hide put errors behind `@noinline` function boundaries.

---

<div class="post-metadata">

### Author: ![Ronis\_BR](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/ronis_br/32/50999_2.png) [@Ronis\_BR](https://discourse.julialang.org/u/Ronis_BR)
#### Post date: [October 1, 2025, 12:43pm UTC](https://discourse.julialang.org/t/help-to-understand-the-performance-gain-when-using-base-throw-boundscheck/132801/7 "2025-10-01T12:43:33Z")

</div>

I see, but what is the explanation? I have no idea why this happens.

---

<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: [October 1, 2025, 12:55pm UTC](https://discourse.julialang.org/t/help-to-understand-the-performance-gain-when-using-base-throw-boundscheck/132801/8 "2025-10-01T12:55:18Z")

</div>

Because `throw` has a ton of complicated crap in it that gets put into the function body. When you lock it behind a `@noinline` function, the optimizer can ignore it, and the function body itself doesn’t take as much room in the instruction cache (because the function contains less instructions).

---

<div class="post-metadata">

### Author: ![Ronis\_BR](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/ronis_br/32/50999_2.png) [@Ronis\_BR](https://discourse.julialang.org/u/Ronis_BR)
#### Post date: [October 1, 2025, 1:08pm UTC](https://discourse.julialang.org/t/help-to-understand-the-performance-gain-when-using-base-throw-boundscheck/132801/9 "2025-10-01T13:08:00Z")

</div>

Awesome! Thanks @Mason !

---

<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: [October 1, 2025, 7:44pm UTC](https://discourse.julialang.org/t/help-to-understand-the-performance-gain-when-using-base-throw-boundscheck/132801/10 "2025-10-01T19:44:56Z")

</div>

> [@Mason](#):
>
> When you lock it behind a `@noinline` function, the optimizer can ignore it, and the function body itself doesn’t take as much room in the instruction cache

Why does it take a `@noinline` line inside a separate error-throwing method’s body to have this effect though? That is, a `@noinline` taking the `throw` call directly didn’t work here. Are there different inlining heuristics there?

---

<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: [October 1, 2025, 8:34pm UTC](https://discourse.julialang.org/t/help-to-understand-the-performance-gain-when-using-base-throw-boundscheck/132801/11 "2025-10-01T20:34:40Z")

</div>

In this scenario, I think it has more to do with `BoundsError` than `throw`, since `BoundsError` has an `::Any` field, so constructing one requires allocations.

This means that there’s a bunch of allocation code (which never gets called) but gets inlined into the function body and that’s what’s causing problems as far as I understand.

---

<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: [October 1, 2025, 8:52pm UTC](https://discourse.julialang.org/t/help-to-understand-the-performance-gain-when-using-base-throw-boundscheck/132801/12 "2025-10-01T20:52:02Z")

</div>

Explains why `Base.throw_boundserror` exists as a separate method from its callee `throw`: a function barrier for the `BoundsError` instantiation too.

---

<div class="post-metadata">

### Author: ![mikmoore](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mikmoore/32/31109_2.png) [@mikmoore](https://discourse.julialang.org/u/mikmoore)
#### Post date: [October 1, 2025, 9:14pm UTC](https://discourse.julialang.org/t/help-to-understand-the-performance-gain-when-using-base-throw-boundscheck/132801/13 "2025-10-01T21:14:47Z")

</div>

Is there a way we can make this happen more automatically? Would adding `@noinline` to the constructors for some of the `subtypes(Exception)` (e.g., ones with non-concrete elements) get us most of the way there?

I.e., does `@boundscheck (i > L.n || j > L.n || i < 1 || j < 1) && @noinline throw(@noinline BoundsError(L, (i, j)))` (added a `@noinline` on `BoundsError`) do any better (with or without the `@noinline` on `throw`)?

If not, is there anything we could do towards this end with a `@throw` macro?

---

<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: [October 1, 2025, 10:08pm UTC](https://discourse.julialang.org/t/help-to-understand-the-performance-gain-when-using-base-throw-boundscheck/132801/14 "2025-10-01T22:08:06Z")

</div>

> [@mikmoore](#):
>
> Would adding `@noinline` to the constructors for some of the `subtypes(Exception)`

I would _assume_ that it’s fine for exceptionally executed branches (`try`-`catch` as routine control flow is much rarer, `error` does `@noinline` for `ErrorException`), but there’s probably a debate on whether that’s generally good. I also just found out that `throw` is a builtin, so I’m not actually sure how inlining works for those.

> [@mikmoore](#):
>
> @noinline throw(@noinline BoundsError(L, (i, j)))

I assumed from the docstring that the first `@noinline` would’ve applied to the `BoundsError` call as well, but evidently not based on the need for `Base.throw_boundserror` or a similar method. I’d think that either needs an implementation fix or a docstring clarification.

```julia-auto
  @noinline block

  Give a hint to the compiler that it should not inline the calls within block.

  # The compiler will try to not inline `f`
  @noinline f(...)

  # The compiler will try to not inline `f`, `g` and `+`
  @noinline f(...) + g(...)

```

Note that this is separate from the zero-argument `@noinline;` that affects the surrounding method body.
