# Bounds check outside loop affects loop performance

**URL:** <https://discourse.julialang.org/t/bounds-check-outside-loop-affects-loop-performance/109524>\
**Category:** Performance\
**Tags:** loops\
**Created:** [January 31, 2024, 4:10pm UTC](https://discourse.julialang.org/t/bounds-check-outside-loop-affects-loop-performance/109524 "2024-01-31T16:10:50Z")\
**Posts on this page:** 1\
**Page:** 1

<div class="post-metadata">

**Author:** ![wheeheee](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/wheeheee/32/36663_2.png) [@wheeheee](https://discourse.julialang.org/u/wheeheee)\
**Post date:** [January 31, 2024, 4:10pm UTC](https://discourse.julialang.org/t/bounds-check-outside-loop-affects-loop-performance/109524/1 "2024-01-31T16:10:50Z")

</div>

While playing around with a function, I noticed that adding a bounds check (`si_check`) had a drastic impact on its performance. Although for `N <= 18`, there is no noticeable performance impact, and the emitted LLVM IR shows that most exceptions from `@nextract ...` have been elided, when `N > 18` (in my benchmarks I use `N = 56`), using `@inbounds` benefits performance, but the bounds check actually drastically slows it down. The output of `@code_llvm` apart from the exceptions is really different too.

Is this expected? Should I file an issue?

```julia
@generated function _filt_fir!(out, b::NTuple{N,T}, x, siarr, col) where {N,T}
    silen = N - 1
    si_end = Symbol(:si_, silen)
    SMALL_FILT_VECT_CUTOFF = 18
    si_check = N > SMALL_FILT_VECT_CUTOFF ? :(nothing) : :(@assert length(siarr) == $silen)

    q = quote
        $si_check
        Base.@nextract $silen si siarr
        for i in axes(x, 1)
            xi = x[i, col]
            val = muladd(xi, b[1], si_1)
            Base.@nexprs $(silen-1) j -> (si_j = muladd(xi, b[j+1], si_{j+1}))
            $si_end = b[N] * xi
            out[i, col] = val
        end
    end

    if N > SMALL_FILT_VECT_CUTOFF
        loop_args = q.args[6].args[2].args
        for i in (2, 10)
            loop_args[i] = :(@inbounds $(loop_args[i]))
        end
    end
    q
end

```

Benchmarks, with the function above unmodified. No `assert ...` in the body.

```julia-repl
julia> x = rand(10_000); out = similar(x);

julia> a = 1.; b = Tuple(rand(56)); si = zeros(55);

julia> @benchmark _filt_fir!($out, $b, $x, $si, $1)
BenchmarkTools.Trial: 10000 samples with 1 evaluation.
 Range (min … max): 26.900 μs … 112.600 μs ┊ GC (min … max): 0.00% … 0.00%
 Time (median): 27.000 μs ┊ GC (median): 0.00%
 Time (mean ± σ): 27.320 μs ± 1.496 μs ┊ GC (mean ± σ): 0.00% ± 0.00%

  ██ ▁ ▆ ▄ ▁▄ ▂
  ██▁█▁█▁▇▇▁▇▁█▁█▇▁▅▁▄▁▃▅▁▆▁▇▁▇▆▁▆▁▆▁▆▁██▁█▁█▁▇▇▁▇▁▇▁▇▆▁▆▁█▁▇▄ █
  26.9 μs Histogram: log(frequency) by time 30.3 μs <

 Memory estimate: 0 bytes, allocs estimate: 0.

```

Benchmark after changing `si_check` to an unconditional `@assert ...`

```julia-repl
julia> @benchmark _filt_fir!($out, $b, $x, $si, $1)
BenchmarkTools.Trial: 10000 samples with 1 evaluation.
 Range (min … max): 68.400 μs … 109.700 μs ┊ GC (min … max): 0.00% … 0.00%
 Time (median): 68.500 μs ┊ GC (median): 0.00%
 Time (mean ± σ): 68.968 μs ± 1.968 μs ┊ GC (mean ± σ): 0.00% ± 0.00%

  █▃ ▂▁ ▁
  ██▃▁▃▁▃▃▁▁▃▆▅▄▆▅▆▆▇▆▆▅▆▆▅▅▇▅▆▇▆▅▅▅▅▄▅▆▅▆▅▅▆██▅▆▄▆▅▆▆▇▆▄▄▅▄▆▅ █
  68.4 μs Histogram: log(frequency) by time 76 μs <

 Memory estimate: 0 bytes, allocs estimate: 0.

```

Platform details, uses AVX-512 instructions.

```julia-repl
julia> versioninfo()
Julia Version 1.10.0
Commit 3120989f39 (2023-12-25 18:01 UTC)
Build Info:
  Official https://julialang.org/ release
Platform Info:
  OS: Windows (x86_64-w64-mingw32)
  CPU: 8 × 11th Gen Intel(R) Core(TM) i5-1135G7 @ 2.40GHz
  WORD_SIZE: 64
  LIBM: libopenlibm
  LLVM: libLLVM-15.0.7 (ORCJIT, tigerlake)
  Threads: 11 on 8 virtual cores
Environment:
  JULIA_CONDAPKG_BACKEND = Null
  JULIA_NUM_THREADS = auto

```
