pic
Personal
Website

9b. Slice Views to Reduce Allocations

PhD in Economics

Introduction

In the section Slices and Views, we defined a slice as a subvector of the parent vector x, such as x[1:2] or x[x .> 0]. Slices create a copy of the data by default and therefore allocate memory. [note] Recall that the only exception is when the slice appears on the left-hand side of =, in which case the slice refers to the original object.

Next, we consider the use of views as an alternative to avoid these allocations. Views allow the user to directly reference the parent object, instead of creating a copy. The approach is useful when the slice is indexed through ranges, although not when the slice employs Boolean indexing.

Finally, we demonstrate that copying data in some occasions may be faster than using views, despite allocating. This follows because vectors create a contiguous block in memory, enabling a more efficient access to their elements.

Views of Slices

The following example shows that views don't allocate memory when the slice is indexed by a range. This can lead to performance improvements compared to regular slices, which create a copy by default.

x = [1, 2, 3]

foo(x) = sum(x[1:2])           # it allocates ONE vector -> the slice 'x[1:2]'
Output in REPL
julia>
@btime foo($x)
  15.015 ns (1 allocation: 80 bytes)
x = [1, 2, 3]

foo(x) = sum(@view(x[1:2]))    # it doesn't allocate
Output in REPL
julia>
@btime foo($x)
  1.200 ns (0 allocations: 0 bytes)

However, views under Boolean indexing won't reduce memory allocations or be more performant. Therefore, don't rely on views of these objects to speed up computations. This fact is illustrated below.

x = rand(1_000)

foo(x) = sum(x[x .> 0.5])
Output in REPL
julia>
@btime foo($x)
  662.500 ns (4 allocations: 8.34 KiB)
x = rand(1_000)

foo(x) = @views sum(x[x .> 0.5])
Output in REPL
julia>
@btime foo($x)
  759.770 ns (4 allocations: 8.34 KiB)

Copying Data May Be Faster

Even with increased memory allocations, copying data can sometimes be faster than using views. The reason is that new vectors create contiguous blocks in memory, which the CPU can access more easily. In contrast, views have to access data that is dispersed in memory, thereby slowing down computations. In some scenarios, the cost of creating a copy may outweigh the cost of accessing scattered data, rendering copies the preferred approach. This point is illustrated below.

x = rand(100_000)

foo(x) = max.(x[1:2:length(x)], 0.5)
Output in REPL
julia>
@btime foo($x)
  30.100 μs (4 allocations: 781.34 KiB)
x = rand(100_000)

foo(x) = max.(@view(x[1:2:length(x)]), 0.5)
Output in REPL
julia>
@btime foo($x)
  151.700 μs (2 allocations: 390.67 KiB)