Hello ,

I tried to optimize my code to reduce GC time. Among other things, I tried to implement “in place” operations, in particular with a long GC function “is_intersect!”, which checks whether a given interval intersects with an interval list . However, the following code structure doesn’t change anything to my GC time compared to a “naive” implementation where I create new variable at each step. Do you see an implementations error in the following code?

A structure I used :

```
using IntervalSets
Base.@kwdef struct S
id::Int16
number_of_interval::Int16
intervals::Vector{ClosedInterval{Float64}}
...
end
```

The `is_intersect!`

function :

```
function is_intersect!(interval_1::Interval, structure_1::S, is_intersect :: Vector{Bool})
is_intersect[1] = false
for interval in structure_1.intervals
if !isempty(intersect(interval_1, interval))
is_intersect[1] = true
end
end
end
```

The main function :

```
function main(structure_1)
compartment_id = 0
t = 0.0
is_intersect = Vector{Bool}(undef, 1)
time_in_compartment = Vector{Float64}(undef, 1)
while t < duration
time_in_compartment[1] = rand(1)[1]
if compartment_id == 0
is_intersect!(t .. (t + time_in_compartment[1]), structure_1, is_intersect)
...
```

thanks !

fdekerm

one easy improvement is to use `rand()`

rather than `rand(1)[1]`

1 Like

Yes, thank you for pointing that out, it was a relic of an earlier version!

Please post enough code so that it can actually be run.

2 Likes

The code is part of a bigger project, so I’ve tried to make a small version that I can share here. The GC time is obviously less marked than on the real version of the code, but do you see areas for improvement/errors in my implementation? For example, does using a mutable struct (struct `cell`

here) and changing the value of its parameters during execution have a strong footprint on the GC?

```
using IntervalSets
using StatsBase: sample, Weights
using ProgressMeter
function is_intersect!(interval::Interval, fractions_list, is_intersect :: Vector{Bool})
is_intersect[1] = false
for fraction in fractions_list
if !isempty(intersect(interval, fraction))
is_intersect[1] = true
end
end
end
Base.@kwdef mutable struct cell
const id::Int
total::Float64 = 0.0
actual_compartment::Int16
end
function walk(cell_id, compartment_ids, fractions_list, duration)
l = cell(id=cell_id,
actual_compartment=0)
is_intersect = Vector{Bool}(undef, 1)
time_in_compartment = Vector{Float64}(undef, 1)
t = 0.0
while t < duration
time_in_compartment[1] = 10 * rand()
if l.actual_compartment == 0.0
is_intersect!(t .. (t + time_in_compartment[1]), fractions_list, is_intersect)
if is_intersect[1]
l.total += rand()
end
end
t += time_in_compartment[1]
l.actual_compartment = sample(compartment_ids)
end
return l.total
end
number_of_cells = 100_000
compartment_ids = [0,1,2,3,4,5]
fractions_list = [10..12, 20..22, 30..32, 40..42, 50..52, 60..62, 70..72, 80..82, 90..92, 100..102]
duration = 100
function main(number_of_cells, compartment_ids, fractions_list, duration)
t = Vector{Float64}(undef, number_of_cells)
p = Progress(number_of_cells) #Progression bar
@time Threads.@threads for cell_id in 1:number_of_cells
tot = walk(cell_id, compartment_ids, fractions_list, duration)
t[cell_id] = tot
next!(p)
end
return (t)
end
```

It seems you allocate twice per `cell`

which should be the two vectors getting allocated inside `walk`

:

```
is_intersect = Vector{Bool}(undef, 1)
time_in_compartment = Vector{Float64}(undef, 1)
```

1 Like

and would you see any solutions to improve this?

In your current code; I can’t see a reason for using a Vector with a single element. Do you extend this vector in your true code? If not, just use a regular variable, or if you need the mutability (because you want to change the value elsewhere, but that could also be restructured) use a `Ref`

instead. But to me nothing there looks like it allocates way to much.

Let’s take a step back though: How did you check for gctime/allocations? If you used `@time`

it is likely that you included compilation time into your results which also inflates the allocations/gctime since the compiler allocates a lot. The first step to optimization is measuring and then optimizing where the gross of allocations (or time spent in general) happens. If you use vscode you can simply call `main`

once to compile and then do `@profview main(...)`

to get a flamegraph showing where time was spent during execution. Usually that is sufficient for optimization pruposes but you can also track allocations line-by-line as explained here. This will hopefully give you a better understanding of where to optimize and if not you can come back with the data and we will help you decipher it

3 Likes