# Large Garbage Collection in for loop if slicing vector

**URL:** <https://discourse.julialang.org/t/large-garbage-collection-in-for-loop-if-slicing-vector/78229>\
**Category:** Performance\
**Created:** [March 21, 2022, 5:31pm UTC](https://discourse.julialang.org/t/large-garbage-collection-in-for-loop-if-slicing-vector/78229 "2022-03-21T17:31:02Z")\
**Posts on this page:** 11\
**Page:** 1

<div class="post-metadata">

**Author:** ![junder873](https://avatars.discourse-cdn.com/v4/letter/j/e95f7d/32.png) [@junder873](https://discourse.julialang.org/u/junder873)\
**Post date:** [March 21, 2022, 5:31pm UTC](https://discourse.julialang.org/t/large-garbage-collection-in-for-loop-if-slicing-vector/78229/1 "2022-03-21T17:31:02Z")

</div>

When creating a slice of a vector in a for loop, it seems like Julia creates a new vector each time which then needs to be dealt with by the garbage collector. In my setting, sometimes the GC can significantly slow down the program. As a MWE:

```julia
function f(data::Vector{Float64}; n=1000)
    out = fill(0.0, n)
    for i in 1:n
        r = rand(1:length(data)-101)
        x = data[r:r+100]
        out[i] = sum(x)
    end
    out
end
@benchmark f_vec(data) setup=(data=randn(10000))
# BenchmarkTools.Trial: 10000 samples with 1 evaluation.
# Range (min … max): 103.800 μs … 1.461 ms ┊ GC (min … max): 0.00% … 80.05%
# Time (median): 111.400 μs ┊ GC (median): 0.00%
# Time (mean ± σ): 131.199 μs ± 80.903 μs ┊ GC (mean ± σ): 6.53% ± 10.09%

# █▇▄▃▄▂▂▁ ▁▁ ▁
# ████████████████▇█▆▆▇▆▅▅▄▅▅▁▃▅▃▃▄▁▁▁▃▁▁▁▁▁▁▁▁▁▁▁▁▃▅▅▇▇▆▆▅▆▆▆ █
# 104 μs Histogram: log(frequency) by time 584 μs <

# Memory estimate: 882.94 KiB, allocs estimate: 1001.

```

The median of that timing is fine, however, the max time with 80% GC creates some problems for me. For example, if I change to `n=10_000_000`, then it takes 30 seconds to run instead of the 1 second implied by the median time. I can also fix this by changing to `x = @view data[r:r+100]`. However, in my more specific setting I cannot guarantee that it will always be continuous slices (so it might be a random set of indices).

So my main question is, why does Julia create a new vector each time for `x` and then need to collect it? Is there a way to specify that I want to overwrite the same place in memory? Would that help? Is there another way to solve this?

---

<div class="post-metadata">

**Author:** ![Jeff\_Emanuel](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/jeff_emanuel/32/15440_2.png) [@Jeff\_Emanuel](https://discourse.julialang.org/u/Jeff_Emanuel)\
**Post date:** [March 21, 2022, 5:42pm UTC](https://discourse.julialang.org/t/large-garbage-collection-in-for-loop-if-slicing-vector/78229/2 "2022-03-21T17:42:14Z")

</div>

Generally, pass an iterator to `sum`.

---

<div class="post-metadata">

**Author:** ![kristoffer.carlsson](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/kristoffer.carlsson/32/22_2.png) [@kristoffer.carlsson](https://discourse.julialang.org/u/kristoffer.carlsson)\
**Post date:** [March 21, 2022, 5:57pm UTC](https://discourse.julialang.org/t/large-garbage-collection-in-for-loop-if-slicing-vector/78229/3 "2022-03-21T17:57:01Z")

</div>

You can use a view even with arbitrary indices.

---

<div class="post-metadata">

**Author:** ![giordano](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/giordano/32/2166_2.png) [@giordano](https://discourse.julialang.org/u/giordano)\
**Post date:** [March 21, 2022, 5:58pm UTC](https://discourse.julialang.org/t/large-garbage-collection-in-for-loop-if-slicing-vector/78229/4 "2022-03-21T17:58:21Z")

</div>

> [@junder873](#):
>
> Is there a way to specify that I want to overwrite the same place in memory? Would that help? Is there another way to solve this?

Like adding a “buffer” argument?

```julia
julia> function g(data::Vector{Float64}, buffer::Vector{Float64}; n=1000)
           out = fill(0.0, n)
           for i in 1:n
               r = rand(1:length(data)-101)
               buffer .= @view data[r:r+100]
               out[i] = sum(buffer)
           end
           out
       end
g (generic function with 1 method)

julia> @benchmark g(data, buffer) setup=(data=randn(10000); buffer=Vector{Float64}(undef, 101))
BenchmarkTools.Trial: 10000 samples with 1 evaluation.
 Range (min … max): 55.861 μs … 165.260 μs ┊ GC (min … max): 0.00% … 0.00%
 Time (median): 57.117 μs ┊ GC (median): 0.00%
 Time (mean ± σ): 60.518 μs ± 6.547 μs ┊ GC (mean ± σ): 0.00% ± 0.00%

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

 Memory estimate: 7.94 KiB, allocs estimate: 1.

```

But just using `x = @view ...` is probably simpler.

---

<div class="post-metadata">

**Author:** ![junder873](https://avatars.discourse-cdn.com/v4/letter/j/e95f7d/32.png) [@junder873](https://discourse.julialang.org/u/junder873)\
**Post date:** [March 21, 2022, 6:04pm UTC](https://discourse.julialang.org/t/large-garbage-collection-in-for-loop-if-slicing-vector/78229/5 "2022-03-21T18:04:37Z")

</div>

Yes, just as I understand it, sometimes doing so can be slower since the slice is not continuous in memory. Though, in my setting, it might be worth it to avoid GC.

---

<div class="post-metadata">

**Author:** ![junder873](https://avatars.discourse-cdn.com/v4/letter/j/e95f7d/32.png) [@junder873](https://discourse.julialang.org/u/junder873)\
**Post date:** [March 21, 2022, 6:11pm UTC](https://discourse.julialang.org/t/large-garbage-collection-in-for-loop-if-slicing-vector/78229/6 "2022-03-21T18:11:48Z")

</div>

I was thinking more like:

```julia
function f(data::Vector{Float64}; n=1000)
    out = fill(0.0, n)
    x = Vector{Float64}(undef, 101)
    for i in 1:n
        r = rand(1:length(data)-101)
        x .= data[r:r+100]
        out[i] = sum(x)
    end
    out
end

```

But that doesn’t result in any differences.

Is there any benefit in passing `buffer` in yours?

---

<div class="post-metadata">

**Author:** ![giordano](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/giordano/32/2166_2.png) [@giordano](https://discourse.julialang.org/u/giordano)\
**Post date:** [March 21, 2022, 6:13pm UTC](https://discourse.julialang.org/t/large-garbage-collection-in-for-loop-if-slicing-vector/78229/7 "2022-03-21T18:13:21Z")

</div>

> [@junder873](#):
>
> Is there any benefit in passing `buffer` in yours?

Only in case you want to reuse the same buffer in other functions, if you use this pattern in different places. Otherwise not really. And I’m still not sure it’s any better than just using `x = @view ...`.

---

<div class="post-metadata">

**Author:** ![junder873](https://avatars.discourse-cdn.com/v4/letter/j/e95f7d/32.png) [@junder873](https://discourse.julialang.org/u/junder873)\
**Post date:** [March 21, 2022, 6:13pm UTC](https://discourse.julialang.org/t/large-garbage-collection-in-for-loop-if-slicing-vector/78229/8 "2022-03-21T18:13:34Z")

</div>

In this case, sum is more of an example than what I am actually trying to do.

---

<div class="post-metadata">

**Author:** ![stevengj](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/stevengj/32/71_2.png) [@stevengj](https://discourse.julialang.org/u/stevengj)\
**Post date:** [March 21, 2022, 7:29pm UTC](https://discourse.julialang.org/t/large-garbage-collection-in-for-loop-if-slicing-vector/78229/9 "2022-03-21T19:29:46Z")

</div>

> [@junder873](#):
>
> `x .= data[r:r+100]`

This still allocates a copy of the `data` slice on the right-hand-side before writing it to `x`.

---

<div class="post-metadata">

**Author:** ![DNF](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/dnf/32/10191_2.png) [@DNF](https://discourse.julialang.org/u/DNF)\
**Post date:** [March 22, 2022, 12:47am UTC](https://discourse.julialang.org/t/large-garbage-collection-in-for-loop-if-slicing-vector/78229/10 "2022-03-22T00:47:01Z")

</div>

If you must collect the data into a buffer, then

```julia
buffer .= @view data[inds] 

```

is the way to go. There’s normally no cost to creating the view, this should just directly retrieve the data points into the buffer.

But if you subsequently iterate just a single time over the buffer, like for example with `sum(buffer)`, then this is just a waste of time, and directly summing the view `sum(@view data[inds])` is as good as it gets. Then you just traverse the data a single time, summing as you go. No need to traverse the data twice if once will do.

If the indices are non-contiguous and you will access them multiple times, collecting them in a buffer _might_ be worth it.

Based on your example, a view seems to be _exactly_ what you want.

---

<div class="post-metadata">

**Author:** ![stevengj](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/stevengj/32/71_2.png) [@stevengj](https://discourse.julialang.org/u/stevengj)\
**Post date:** [March 22, 2022, 1:00am UTC](https://discourse.julialang.org/t/large-garbage-collection-in-for-loop-if-slicing-vector/78229/11 "2022-03-22T01:00:13Z")

</div>

> [@DNF](#):
>
> If the indices are non-contiguous and you will access them multiple times, collecting them in a buffer _might_ be worth it.

PS. If you have an array of indices (instead of a range), it might be worthwhile to forgo the index array entirely, and just compute the desired indices/elements one by one in a loop (either to store into a buffer for subsequent calculations, or to directly calculate something like a sum with them). There’s no need to bend over backwards to avoid writing your own performance-critical inner loops.
