pic
Personal
Website

9a. Objects Allocating Memory

PhD in Economics

Interpreting Memory Allocation

Recall that memory allocations can occur on either the heap or the stack, with Julia exclusively referring to memory allocations as those on the heap. These are the ones to watch out for, as they can significantly slow down computations. In the following, we present several objects avoiding the heap for their storage. Afterwards, we'll introduce objects that do allocate on the heap.

Numbers, Tuples, Named Tuples, and Ranges Don't Allocate

Objects that don't allocate memory include:

  • numbers

  • tuples

  • named tuples

  • ranges

This property determines that creating, accessing, and operating on these objects don't allocate either. This is demonstrated below.

function foo()
    x = 1; y = 2
    
    x + y
end
Output in REPL
julia>
@btime foo()
  0.800 ns (0 allocations: 0 bytes)
function foo()
    tup = (1,2,3)

    tup[1] + tup[2] * tup[3]
end
Output in REPL
julia>
@btime foo()
  0.800 ns (0 allocations: 0 bytes)
function foo()
    nt = (a=1, b=2, c=3)

    nt.a + nt.b * nt.c
end
Output in REPL
julia>
@btime foo()
  0.800 ns (0 allocations: 0 bytes)
function foo()
    rang = 1:3

    rang[1] + rang[2] * rang[3]
end
Output in REPL
julia>
@btime foo()
  0.800 ns (0 allocations: 0 bytes)

Arrays and Slices Do Allocate Memory

Array creation allocates memory, even if the result is not stored. For instance, executing the following function allocates memory.

Vector Creation
foo()  = [1,2,3]
Output in REPL
julia>
@btime foo()
  13.714 ns (1 allocation: 80 bytes)

Another common operation that allocates memory is slicing, as it creates a copy of an array by default. The only case where no memory allocation occurs is when we access a single element of an array.

x      = [1,2,3]

foo(x) = x[1:2]                 # ONE allocation, since ranges don't allocate (but 'x[1:2]' itself does)
Output in REPL
julia>
@btime foo($x)
  16.116 ns (1 allocation: 80 bytes)
x      = [1,2,3]

foo(x) = x[[1,2]]               # TWO allocations (one for '[1,2]' and another for 'x[[1,2]]' itself)
Output in REPL
julia>
@btime foo($x)
  31.759 ns (2 allocations: 160 bytes)
x      = [1,2,3]

foo(x) = x[1] * x[2] + x[3]
Output in REPL
julia>
@btime foo($x)
  1.400 ns (0 allocations: 0 bytes)

Array creation occurs not only through slicing, but also with array comprehensions and broadcasting operations. In the case of broadcasting, this takes place even when the output vector is temporary, as demonstrated in the tab "Broadcasting 2".

foo()  = [a for a in 1:3]
Output in REPL
julia>
@btime foo()
  13.514 ns (1 allocation: 80 bytes)
x      = [1,2,3]
foo(x) = x .* x
Output in REPL
julia>
@btime foo($x)
  15.916 ns (1 allocation: 80 bytes)
x      = [1,2,3]
foo(x) = sum(x .* x)                # 1 allocation from temporary vector 'x .* x'
Output in REPL
julia>
@btime foo($x)
  21.242 ns (1 allocation: 80 bytes)