How to concatenate matrices views without copying memory?

Using vcat to catenate views leads to memory allocation which is contrary to the original intent of @views. CatViews.jl can address the issue to some extent, but it is only applicable to vectors. I wonder if there is a way to concatenate the views of two matrices while avoiding memory copying.

using CatViews
using BenchmarkTools
a = rand(2000,2)
b = rand(2000,2)
@btime c = vcat(@view(a[1:1000,:]),@view(b[1:1000,:])) # 10 allocations: 31.61 KiB
c = vcat(@view(a[1:1000,:]),@view(b[1:1000,:]))
d = CatView(@view(a[1:1000,:]),@view(b[1:1000,:])) # 4000-element CatView{2, Float64}
findfirst(x->x==a[1,2],c) # (1,2)
findfirst(x->x==a[1,2],reshape(d,:,2)) # (1001,1)

A possible solution for size(A)=2:

function CatView(A::SubArray{T,2,Matrix{T}}, B::SubArray{T,2,Matrix{T}}) where {T}
    size(A,2)==2&&return reshape(
        CatView(@view(A[:, 1]), @view(B[:, 1]), @view(A[:, 2]), @view(B[:, 2])),
        :,
        2,
    )
end

Maybe you want LazyArrays.jl? But timing the construction isn’t the whole story – composite arrays like this are typically much slower to access, which may cancel out any savings. (This is generally not true of views, which is roughly why Base has SubArray but no CatView-like types.)

using LazyArrays, BenchmarkTools
a = rand(2000,2);
b = rand(2000,2);

c1 = @btime @views vcat($a[1:1000,:], $b[1:1000,:]); # 2μs
summary(c1)
c2 = @btime @views Vcat($a[1:1000,:], $b[1:1000,:]); # 3ns, much faster
c1 == c2  # true

@btime sum(abs2, $c1)  # 377.8 ns
@btime sum(abs2, $c2)  # 2.824 μs, slower overall (at least min time)
5 Likes

Thank you for the reminder! Accessing CatView is indeed extremely slow, so the proposed solution might not be a good idea after all. Perhaps the memory copying here is worth it…

function CatView(A::SubArray{T,2,Matrix{T}}, B::SubArray{T,2,Matrix{T}}) where {T}
    size(A,2)==2&&return reshape(
        CatView(@view(A[:, 1]), @view(B[:, 1]), @view(A[:, 2]), @view(B[:, 2])),
        :,
        2,
    )
end
a = rand(2000,2)
b = rand(2000,2)
c1 = @views CatView(a[1:1000,:],b[1:1000,:])
c2 = @views vcat(a[1:1000,:],b[1:1000,:])
@btime sum($c1) # 2.599 ms (100888 allocations: 1.72 MiB) Catastrophic...
@btime sum($c2) # 270.470 ns (0 allocations: 0 bytes)