# The Case of the Phantom Allocations 👻

**URL:** https://discourse.julialang.org/t/the-case-of-the-phantom-allocations/133183
**Category:** General Usage
**Tags:** question
**Created:** [October 15, 2025, 7:17pm UTC](https://discourse.julialang.org/t/the-case-of-the-phantom-allocations/133183 "2025-10-15T19:17:55Z")
**Posts on this page:** 8
**Page:** 1

<div class="post-metadata">

### Author: ![Matt\_jl](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/matt_jl/32/52364_2.png) [@Matt\_jl](https://discourse.julialang.org/u/Matt_jl)
#### Post date: [October 15, 2025, 7:17pm UTC](https://discourse.julialang.org/t/the-case-of-the-phantom-allocations/133183/1 "2025-10-15T19:17:55Z")

</div>

Hey Julia experts,

I have a performance puzzle. This function is designed to be completely allocation-free, but `@benchmark` tells a different story. I’m hoping a fresh pair of eyes can spot the problem.

This is the function:

```julia-auto

using StatsBase

_default_reducer(::Val{:median}) = median!
_default_reducer(::Val{:mean}) = mean
_default_reducer(::Val{:std}) = std

function sigma_clip!(x::AbstractVector{T},
    mask::BitVector,
    buffer::AbstractVector{T};
    upper::Real=3,
    lower::Real=3,
    cent_func::Symbol=:median,
    std_func::Symbol=:std,
    maxiter::Int=5) where {T<:Real}

    maxiter > 0 || throw(ArgumentError("maxiter must be positive"))
    upper > 0 && lower > 0 || throw(ArgumentError("upper and lower must be positive"))

    cent_reducer = _default_reducer(Val(cent_func))
    std_reducer = _default_reducer(Val(std_func))

    # initialize buffer following mask (isfinite) to avoid cal reducer over NaN or Inf
    N = 0
    @inbounds for i in eachindex(x)
        xi = x[i]
        if isfinite(xi)
            N += 1
            buffer[N] = xi
            mask[i] = true
        else
            mask[i] = false
        end
    end

    N == 0 && return mask

    iter = 0
    @inbounds while iter < maxiter
        # calculate statistic
        w = view(buf, 1:N)

        c = cent_reducer(w)
        s = std_reducer(w)
        iszero(s) && return mask

        upper_bound = c + s * upper
        lower_bound = c - s * lower

        N = 0
        n_clipped = 0

        @inbounds for i in eachindex(x)
            if mask[i]
                xi = x[i]
                if xi > upper_bound || xi < lower_bound
                    mask[i] = false
                    n_clipped += 1
                else
                    N += 1
                    buffer[N] = xi
                end
            end
        end

        n_clipped == 0 && return mask
        iter += 1
    end

    return mask
end

```

The Evidence (The Benchmark):

```julia-auto
@benchmark sigma_clip!(x, y, buf;upper=1.5, lower=1.5, cent_func=:mean) setup=begin
           x = rand(100)
           y = trues(100) 
           buf = rand(100)
       end
BenchmarkTools.Trial: 10000 samples with 8 evaluations per sample.
 Range (min … max): 3.302 μs … 254.620 μs ┊ GC (min … max): 0.00% … 97.24%
 Time (median): 5.255 μs ┊ GC (median): 0.00%
 Time (mean ± σ): 5.725 μs ± 4.792 μs ┊ GC (mean ± σ): 3.23% ± 4.42%

                  ▁▇█▄ ▂
  ▂▂▁▁▂▂▁▁▁▁▁▁▁▂▂▄██████▇██▇▅▄▃▃▃▄▄▄▄▄▄▄▃▃▃▃▃▃▃▃▃▃▃▂▂▂▂▂▂▂▂▂▂ ▃
  3.3 μs Histogram: frequency by time 8.42 μs <

 Memory estimate: 5.50 KiB, allocs estimate: 346.

```

I’ve ruled out the usual suspects (global variables, benchmark setup for mutating functions). What am I missing?

Thanks for any clues!

---

<div class="post-metadata">

### Author: ![hexaeder](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/hexaeder/32/24403_2.png) [@hexaeder](https://discourse.julialang.org/u/hexaeder)
#### Post date: [October 15, 2025, 7:33pm UTC](https://discourse.julialang.org/t/the-case-of-the-phantom-allocations/133183/2 "2025-10-15T19:33:14Z")

</div>

I think

```julia-auto
cent_reducer = _default_reducer(Val(cent_func))

```

Is type unstable because `Val(runtime-thing)` generates a type which depends on a runtime value, therefore every call of the reducer might me type unstable.  
Try hardcoding it

```julia-auto
cent_reducer = median!

```

To see if it changes anything.

Also check out [GitHub - JuliaDebug/Cthulhu.jl: The slow descent into madness](https://github.com/JuliaDebug/Cthulhu.jl) to see the inferred types throughout the function.

---

<div class="post-metadata">

### Author: ![Matt\_jl](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/matt_jl/32/52364_2.png) [@Matt\_jl](https://discourse.julialang.org/u/Matt_jl)
#### Post date: [October 15, 2025, 7:37pm UTC](https://discourse.julialang.org/t/the-case-of-the-phantom-allocations/133183/3 "2025-10-15T19:37:29Z")

</div>

I tried to hard-code the center function and the std function, like this:

```julia-auto
cent_reducer = mean
    std_reducer = std

```

but got this:

```julia-auto
@benchmark sigma_clip!($x, $mask, $buf ;upper=1.5, lower=1.5, cent_func=$:mean)
BenchmarkTools.Trial: 10000 samples with 4 evaluations per sample.
 Range (min … max): 7.188 μs … 346.198 μs ┊ GC (min … max): 0.00% … 96.71%
 Time (median): 7.979 μs ┊ GC (median): 0.00%
 Time (mean ± σ): 8.351 μs ± 5.017 μs ┊ GC (mean ± σ): 2.12% ± 4.58%

               ▃█▆█▁
  ▁▁▁▂▂▅▅▆▆▅▄▆▇█████▇▅▃▄▄▃▄▅▃▄▄▃▃▃▅▇▇▇█▅▄▃▂▂▂▂▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ ▃
  7.19 μs Histogram: frequency by time 9.86 μs <

 Memory estimate: 13.31 KiB, allocs estimate: 837.

```

Also: the benchmark was done in a fresh new REPL

---

<div class="post-metadata">

### Author: ![brianguenter](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/brianguenter/32/29519_2.png) [@brianguenter](https://discourse.julialang.org/u/brianguenter)
#### Post date: [October 15, 2025, 7:39pm UTC](https://discourse.julialang.org/t/the-case-of-the-phantom-allocations/133183/4 "2025-10-15T19:39:11Z")

</div>

Maybe this line:

```julia
 w = view(buf, 1:N)

```

The docs say that view needs to allocate a subarray object. The compiler may be smart enough to stack allocate this or it may get put on the heap. Perhaps the latter case is happening 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: [October 15, 2025, 7:47pm UTC](https://discourse.julialang.org/t/the-case-of-the-phantom-allocations/133183/5 "2025-10-15T19:47:11Z")

</div>

> [@Matt\_jl](#):
>
> ` w = view(buf, 1:N)`

here it should be `buffer` and not `buf` (I think). Then `buf` is just some non-constant global variable you defined outside the function, and that is allocating.

---

<div class="post-metadata">

### Author: ![hexaeder](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/hexaeder/32/24403_2.png) [@hexaeder](https://discourse.julialang.org/u/hexaeder)
#### Post date: [October 15, 2025, 7:51pm UTC](https://discourse.julialang.org/t/the-case-of-the-phantom-allocations/133183/6 "2025-10-15T19:51:53Z")

</div>

Not at a pc right now so unfortunately I cannot try it on my own. Maybe for benchmarking try to set a random seed and also set maxiter to 1, if you allocate/dynamic dispatch every iteration, the result might depend on the input and thus the number of iterations more than on the code.  
I would be quite surprised if the hard coded version had worse performance for the exact same input.  
If Cthulhu is intimidating (been there), another classic approach would be to comment out everything and slowly bring back the functionality line by line to see which operations introduce the allocations.

---

<div class="post-metadata">

### Author: ![Matt\_jl](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/matt_jl/32/52364_2.png) [@Matt\_jl](https://discourse.julialang.org/u/Matt_jl)
#### Post date: [October 15, 2025, 7:52pm UTC](https://discourse.julialang.org/t/the-case-of-the-phantom-allocations/133183/7 "2025-10-15T19:52:32Z")

</div>

oooh.  
You are right!  
It was a typo all the time along…

```julia-auto
@benchmark sigma_clip!($x, $mask, $buf ;upper=1.5, lower=1.5, maxiter=100, cent_func=$:mean)
BenchmarkTools.Trial: 10000 samples with 7 evaluations per sample.
 Range (min … max): 4.292 μs … 12.536 μs ┊ GC (min … max): 0.00% … 0.00%
 Time (median): 4.333 μs ┊ GC (median): 0.00%
 Time (mean ± σ): 4.439 μs ± 242.706 ns ┊ GC (mean ± σ): 0.00% ± 0.00%

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

 Memory estimate: 0 bytes, allocs estimate: 0.

```

---

<div class="post-metadata">

### Author: ![Matt\_jl](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/matt_jl/32/52364_2.png) [@Matt\_jl](https://discourse.julialang.org/u/Matt_jl)
#### Post date: [October 15, 2025, 7:52pm UTC](https://discourse.julialang.org/t/the-case-of-the-phantom-allocations/133183/8 "2025-10-15T19:52:44Z")

</div>

Thank You so much!
