# Does calling a function with no memory allocations create an allocation?

**URL:** <https://discourse.julialang.org/t/does-calling-a-function-with-no-memory-allocations-create-an-allocation/111987>\
**Category:** Performance\
**Tags:** question, memory-allocation\
**Created:** [March 22, 2024, 6:21pm UTC](https://discourse.julialang.org/t/does-calling-a-function-with-no-memory-allocations-create-an-allocation/111987 "2024-03-22T18:21:24Z")\
**Posts on this page:** 6\
**Page:** 1

<div class="post-metadata">

**Author:** ![OmerPaz](https://avatars.discourse-cdn.com/v4/letter/o/ccd318/32.png) [@OmerPaz](https://discourse.julialang.org/u/OmerPaz)\
**Post date:** [March 22, 2024, 6:21pm UTC](https://discourse.julialang.org/t/does-calling-a-function-with-no-memory-allocations-create-an-allocation/111987/1 "2024-03-22T18:21:24Z")

</div>

Hi all,

I am running a numerical simulation in which I document my results in a pre-allocated structure, Obs:

```julia
mutable struct Obs{T}
    name::String
    data::Array{T}
    num_of_measure::Int64
    c_num_of_measure::Int64
 end

```

For this example, I will measure scalars:

```julia
measurements = Obs{Float64}("scalar",fill(0.0,1000),1000,0);

```

by updating this structure with a function:

```julia
function take_measurement!(obs_data::Obs{Float64},data::Float64) 
    obs_data.c_num_of_measure+=1
    obs_data.data[obs_data.c_num_of_measure]=data
end  

```

My question is why does calling this function allocates memory, and whether or not it is optimal. I get (after compiling the function):

```julia
@time for i =1:1000
   take_measurement!(measurements,1.0)
end
0.000060 seconds (1.49 k allocations: 23.266 KiB)

```

Can anyone explain where exactly is this memory coming from? Is there a better way of measuring my data?

To anyone reading the solution: note that this structure is unnecessary as noted in the comments

Thanks,  
Omer

---

<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:** [March 22, 2024, 7:20pm UTC](https://discourse.julialang.org/t/does-calling-a-function-with-no-memory-allocations-create-an-allocation/111987/2 "2024-03-22T19:20:14Z")

</div>

> [@OmerPaz](#):
>
> ```julia
> mutable struct Obs{T}
> name::String
> data::Array{T}
> num_of_measure::Int64
> c_num_of_measure::Int64
> end
> 
> ```

`Array{T}` is a non-concrete type. Thus, when the code fetches this value from the struct is cannot be certain how many dimensions it has. This results in dynamic dispatch. The allocations should disappear if you use `Vector{T}` (or equivalently: `Array{T,1}`).

---

<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, 2024, 7:30pm UTC](https://discourse.julialang.org/t/does-calling-a-function-with-no-memory-allocations-create-an-allocation/111987/3 "2024-03-22T19:30:07Z")

</div>

> [@mikmoore](#):
>
> The allocations should disappear if you use `Vector{T}` (or equivalently: `Array{T,1}`).

Also, be careful benchmarking in global scope. It’s safest to use e.g. BenchmarkTools.jl and do

```julia
@btime take_measurement!($measurements,1.0)

```

where `@btime` will automatically run the benchmark multiple times to get good timing resolution, and the `$` will eliminate any cost from using a global variable `measurements`. (You could also use the `@b` macro from the Chairmarks.jl package.)

---

<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:** [March 22, 2024, 7:33pm UTC](https://discourse.julialang.org/t/does-calling-a-function-with-no-memory-allocations-create-an-allocation/111987/4 "2024-03-22T19:33:18Z")

</div>

By the way, it looks like you’re unnecessarily recreating the machinery of `Vector` here. It has the ability to over-allocate and then have entries added efficiently over time. For example:

```julia
struct Obs{T}
    name::String
    data::Vector{T}
 end

measurements = Obs{Float64}("scalar",empty!(Vector{Float64}(undef,1000))); # allocate space for 1000 elements but then drop them

function take_measurement!(obs_data::Obs, data) 
    push!(obs_data.data, data)
end

clear!(obs::Obs) = empty!(obs.data)

clear!(measurements)
@time for i =1:1000 # run once to compile
   take_measurement!(measurements,1.0)
end

clear!(measurements)
@time for i =1:1000 # run again for speed
   take_measurement!(measurements,1.0)
end

```

gives me

```julia
  0.003733 seconds (2.02 k allocations: 140.328 KiB, 98.84% compilation time)
  0.000026 seconds

```

Note that, with this version, you aren’t limited to just the 1000 measurements you initially planned for. It can add new measurements until you run out of memory. When you `clear!` it, it will usually keep that memory around so that you don’t need to reallocate it if you start a new set later.

Also, with those extra fields gone it looks like you can use a `struct` rather than `mutable struct`. I usually find `struct` easier, safer, and faster to work with.

---

<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:** [March 22, 2024, 7:41pm UTC](https://discourse.julialang.org/t/does-calling-a-function-with-no-memory-allocations-create-an-allocation/111987/5 "2024-03-22T19:41:29Z")

</div>

> [@stevengj](#):
>
> `@btime take_measurement!($measurements,1.0)`

Do note that, in this case, this will not reset the vector between runs and its memory use will grow over the benchmark. With the original implementation, the benchmark crashes when the vector exceeds the allocated 1000 elements. Benchmarking with mutable objects is a little more tedious than usual. (obviously, `stevengj` knows this and just overlooked it this time – I say this for others’ benefit).

My revised version that expands the vector via `push!` does not suffer this limitation. But still, if one prefers to avoid measuring the cost of increasingly large re-allocations, one can `clear!` it periodically. Like this:

```julia-repl
julia> using BenchmarkTools

julia> @btime take_measurement!($measurements,1.0) setup=(clear!(measurements))
  6.807 ns (0 allocations: 0 bytes)

```

---

<div class="post-metadata">

**Author:** ![OmerPaz](https://avatars.discourse-cdn.com/v4/letter/o/ccd318/32.png) [@OmerPaz](https://discourse.julialang.org/u/OmerPaz)\
**Post date:** [March 22, 2024, 7:54pm UTC](https://discourse.julialang.org/t/does-calling-a-function-with-no-memory-allocations-create-an-allocation/111987/6 "2024-03-22T19:54:47Z")

</div>

Thank you, I did not know this possibility was built in Vector.
