# Unexpected memory allocation behavior

Hello. I am using julia `1.7.1`.

I am facing trouble trying to understand an unexpected memory allocation in the below simplified and self-contained program:

``````struct VerticalLine{I}
i_min::I
i_max::I
j::I
end

struct ParallelVerticalLines{I}
i::I
j::I
height::I
width::I
end

function put_pixel!(image::AbstractMatrix, i, j, color)
image[i, j] = color
return nothing
end

function draw!(f::Function, image::AbstractMatrix, shape::VerticalLine, color)
i_min = shape.i_min
i_max = shape.i_max
j = shape.j

for i in i_min:i_max
f(image, i, j, color)
end

return nothing
end

function draw!(f::Function, image::AbstractMatrix, shape::ParallelVerticalLines, color)
i_min = shape.i
i_max = shape.i + shape.height - one(shape.height)
# i_max = shape.i

j_min = shape.j
j_max = shape.j + shape.width - one(shape.width)
# j_max = shape.j

shape1 = VerticalLine(i_min, i_max, j_min)
draw!(f, image, shape1, color)

shape2 = VerticalLine(i_min, i_max, j_max)
draw!(f, image, shape2, color)

return nothing
end

const shape = ParallelVerticalLines(1, 1, 3, 3)

const image1 = zeros(UInt32, 32, 32)
const color1 = 0x00ffffff

draw!(put_pixel!, image1, shape, color1)

println(@allocated draw!(put_pixel!, image1, shape, color1))

const image2 = falses(32, 32)
const color2 = true

draw!(put_pixel!, image2, shape, color2)

println(@allocated draw!(put_pixel!, image2, shape, color2))
``````

Here are my observations that are puzzling to me:

1. The `draw!` function allocates memory in the `UInt32` case but not in the `Bool` case.
2. When I comment out `i_max = shape.i + shape.height - one(shape.height)` and `j_max = shape.j + shape.width - one(shape.width)`, and replace them with `i_max = shape.i` and `j_max = shape.j` respectively, the memory allocation vanishes.
3. When I comment out `shape2 = VerticalLine(i_min, i_max, j_max)` and `draw!(f, image, shape2, color)`, the memory allocation again vanishes.
4. `@code_warntype draw!(put_pixel!, image1, VerticalLine(1, 3, 1), color1)` and `@code_warntype draw!(put_pixel!, image2, VerticalLine(1, 3, 1), color2)` have a `Union` type inferred for an automatically named local variable. I donâ€™t understand what this local variable is and why it is not inferred correctly.

`@code_warntype draw!(put_pixel!, image1, shape, color1)` and `@code_warntype draw!(put_pixel!, image2, shape, color2)` shows that all types seem to be inferred. I donâ€™t see any obvious type instability that might be causing the above issues.

It would be great if someone could help me explain the reasons behind my observations. Thanks!

Here is the output of `versioninfo()`, if needed:

``````julia> versioninfo()
Julia Version 1.7.1
Commit ac5cc99908 (2021-12-22 19:35 UTC)
Platform Info:
OS: Linux (x86_64-pc-linux-gnu)
CPU: Intel(R) Core(TM) i7-6500U CPU @ 2.50GHz
WORD_SIZE: 64
LIBM: libopenlibm
LLVM: libLLVM-12.0.1 (ORCJIT, skylake)
``````

Try the above, because of

https://docs.julialang.org/en/v1/manual/performance-tips/#Be-aware-of-when-Julia-avoids-specializing

(didnâ€™t test)

4 Likes

I get a warning because you define `image1` and `image2` as `const` and later assign values to them. Removing the `const` changes the picture, both allocate

``````  340.092 ns (4 allocations: 96 bytes)
321.888 ns (2 allocations: 64 bytes)
``````

Still puzzling why the number of allocations changes, same for the number of bytes in relation to UInt32 and Bool.

Thanks a lot for your help @lmiq ! Doing as you suggested doesnâ€™t allocate any memory.

1 Like

Hello @Bardo , I am running the program as a script, where I do not get the warning you mention. The warning might occur in case you include the script more than once in the REPL.

I am only assigning `image1` and `image2` once in the script, and that is while defining them.

Bingo.

@lmiq How does your type hint help here? - `F` is not used elsewhere.
How could one have found this solution?

I found that not long time ago by asking something similar here (but it is on the performance tips, as I llinked above).

That is a special case of non-specialization: the function is just passed around to another function. Since functions have each their own type, having a specialized method (a whole compilation) for each type of function would be counter-productive. Thus, the default is to not specialize, and that probably boxes the function, creating the allocation. In terms of performance that is probably harmless, unless in very special cases where the function is passed around to another function within a critical loop (and even there, I donâ€™t think it had a measurable performance penalty in my case). But, if one wants the function to specialize to every different type of function input, the parameterization of the type (`::F where F` syntax) is a way to force that, and it is fine except if the use case expects many different functions to be passed around.

3 Likes