Memory allocation while using @views

I found an issue while using @views, which may help others speed up their code. Let us consider this example of code.

len = 5000
const arr1 = Array{Float64, 2}(undef, 10, len)
const arr2 = Array{Float64, 2}(undef, 10, len)
@time for i in 1:len
                @views arr1[:] .*= arr2[:]
      end

The output is “1.454046 seconds (53.98 k allocations: 1.864 GiB, 12.21% gc time)”. As we can see here it allocates 1.864GiB memory, and GC worked here. I was wondering if this happens due to the design of @views or it is simply a bug (in which case, I can open an issue).
I also found two ways how can we avoid this problem. We can write “@views arr1[:,:] .*= arr2[:,:]” instead of line 5 and the output will be “0.235785 seconds (13.98 k allocations: 296.562 KiB)”.
Or we can write 5th line this way “(@view arr1[:]) .*= @view arr2[:]” and the output will be “0.363380 seconds (33.98 k allocations: 1.053 MiB)”.
So as you can see in both cases there is no such huge allocation and the running time is much less than in the original example. There is no such issue with 1-dimensional arrays.

It seems like you want your line to be (rather than what you did read the whole 2D array 5000 times, len times, giving different result):

@views arr1[:, i] .*= arr2[:, i]

or even skip the (explicit) loop, just do:

@time @views arr1[:, :] .*= arr2[:, :]
  0.000112 seconds

I don’t think @views makes the LHS of an assignment like += into a view:

 @macroexpand @views a[:] += b[:]

:(let var"##a#296" = a, var"##i#297" = (:)
      var"##a#296"[var"##i#297"] = (Base.maybeview)(var"##a#296", var"##i#297") + (Base.maybeview)(b, :)
  end)

This is especially easier for updating operations like .+= , where you can’t put view on the left-hand side (as discussed on discourse).

1 Like

Perhaps this misses the point, but in this particular case, at least, you can write

arr1 .*= arr2

without any @views and get zero allocations.

I’m not able to get zero allocations as long as I’m using views, which I don’t quite understand.

2 Likes

Right, I wanted to check:

julia> @btime arr1 .*= arr2;
  37.067 μs (0 allocations: 0 bytes)

julia> @btime @views arr1[:, :] .*= arr2[:, :];
  36.371 μs (0 allocations: 0 bytes)

This is not a meaningful difference; on my (loaded) machine. I still wanted to see the minimum time and was surprised to see:

julia> @benchmark arr1 .*= arr2;

no result, vs. e.g.:

julia> @benchmark sin(0)
BenchmarkTools.Trial: 
  memory estimate:  0 bytes
  allocs estimate:  0
  --------------
  minimum time:     1.880 ns (0.00% GC)
  median time:      1.954 ns (0.00% GC)
  mean time:        1.999 ns (0.00% GC)
  maximum time:     17.903 ns (0.00% GC)
  --------------
  samples:          10000
  evals/sample:     1000

Huh!? What the …? I ran this exact code before answering, and got allocations. Now I ran it again and got zero? What on earth could be going on?

Edit: Apparently

@views arr1[:] .*= arr2[:]

causes allocations (for matrices), and

@views arr1[:, :] .*= arr2[:, :]

does not.