From ef704be860fd98ddc3d035bedece8083261a697f Mon Sep 17 00:00:00 2001 From: lkdvos Date: Wed, 7 Oct 2026 09:48:06 -0400 Subject: [PATCH 1/2] Avoid redundant normalization when transforming strided views --- src/stridedview.jl | 57 +++++++++++++++++++++++++++++++++++----------- test/runtests.jl | 6 +++++ 2 files changed, 50 insertions(+), 13 deletions(-) diff --git a/src/stridedview.jl b/src/stridedview.jl index 906b041..e9f6099 100644 --- a/src/stridedview.jl +++ b/src/stridedview.jl @@ -71,7 +71,9 @@ function StridedView(a::Base.ReinterpretArray{T, N}) where {T, N} throw(ArgumentError("Cannot create StridedView with reinterpretation from $S to $T")) b.op isa FN || throw(ArgumentError("Cannot create StridedView with reinterpretation from view with non-identity operation")) - return StridedView{T}(b.parent, size(b), strides(b), offset(b)) + return StridedView{T, ndims(b), typeof(b.parent), FN}( + b.parent, size(b), strides(b), offset(b), identity + ) end # trait @@ -140,10 +142,12 @@ end # Indexing with slice indices to create a new view. function Base.getindex(a::StridedView{T, N}, I::Vararg{SliceIndex, N}) where {T, N} - return StridedView{T}( + newsize = _computeviewsize(a.size, I) + newstrides = _normalizestrides(newsize, _computeviewstrides(a.strides, I)) + return StridedView{T, length(newsize), typeof(a.parent), typeof(a.op)}( a.parent, - _computeviewsize(a.size, I), - _computeviewstrides(a.strides, I), + newsize, + newstrides, a.offset + _computeviewoffset(a.strides, I), a.op ) @@ -170,20 +174,27 @@ end #---------------------------------------------------------------------------- Base.conj(a::StridedView{<:Real}) = a function Base.conj(a::StridedView{T}) where {T <: Complex} - return StridedView{T}(a.parent, a.size, a.strides, a.offset, _conj(a.op)) + newop = _conj(a.op) + return StridedView{T, ndims(a), typeof(a.parent), typeof(newop)}( + a.parent, a.size, a.strides, a.offset, newop + ) end function Base.conj(a::StridedView) S = Base.promote_op(a.op, eltype(a)) newop = _conj(a.op) T = Base.promote_op(newop, S) - return StridedView{T}(a.parent, a.size, a.strides, a.offset, newop) + return StridedView{T, ndims(a), typeof(a.parent), typeof(newop)}( + a.parent, a.size, a.strides, a.offset, newop + ) end function Base.permutedims(a::StridedView{T, N}, p) where {T, N} _isperm(N, p) || throw(ArgumentError("Invalid permutation of length $N: $p")) newsize = ntuple(n -> size(a, p[n]), Val(N)) newstrides = ntuple(n -> stride(a, p[n]), Val(N)) - return StridedView{T}(a.parent, newsize, newstrides, a.offset, a.op) + return typeof(a)( + a.parent, newsize, _normalizestrides(newsize, newstrides), a.offset, a.op + ) end LinearAlgebra.transpose(a::StridedView{<:Number, 2}) = permutedims(a, (2, 1)) @@ -192,13 +203,21 @@ function LinearAlgebra.adjoint(a::StridedView{<:Any, 2}) # act recursively, like S = Base.promote_op(a.op, eltype(a)) newop = _adjoint(a.op) T = Base.promote_op(newop, S) - return permutedims(StridedView{T}(a.parent, a.size, a.strides, a.offset, newop), (2, 1)) + return permutedims( + StridedView{T, ndims(a), typeof(a.parent), typeof(newop)}( + a.parent, a.size, a.strides, a.offset, newop + ), (2, 1) + ) end function LinearAlgebra.transpose(a::StridedView{<:Any, 2}) # act recursively, like Base S = Base.promote_op(a.op, eltype(a)) newop = _transpose(a.op) T = Base.promote_op(newop, S) - return permutedims(StridedView{T}(a.parent, a.size, a.strides, a.offset, newop), (2, 1)) + return permutedims( + StridedView{T, ndims(a), typeof(a.parent), typeof(newop)}( + a.parent, a.size, a.strides, a.offset, newop + ), (2, 1) + ) end Base.map(::FC, a::StridedView{<:Real}) = a @@ -206,15 +225,24 @@ Base.map(::FT, a::StridedView{<:Number}) = a Base.map(::FA, a::StridedView{<:Number}) = conj(a) function Base.map(::FC, a::StridedView) T = Base.promote_op(conj, eltype(a)) - return StridedView{T}(a.parent, a.size, a.strides, a.offset, _conj(a.op)) + newop = _conj(a.op) + return StridedView{T, ndims(a), typeof(a.parent), typeof(newop)}( + a.parent, a.size, a.strides, a.offset, newop + ) end function Base.map(::FT, a::StridedView) T = Base.promote_op(transpose, eltype(a)) - return StridedView{T}(a.parent, a.size, a.strides, a.offset, _transpose(a.op)) + newop = _transpose(a.op) + return StridedView{T, ndims(a), typeof(a.parent), typeof(newop)}( + a.parent, a.size, a.strides, a.offset, newop + ) end function Base.map(::FA, a::StridedView) T = Base.promote_op(adjoint, eltype(a)) - return StridedView{T}(a.parent, a.size, a.strides, a.offset, _adjoint(a.op)) + newop = _adjoint(a.op) + return StridedView{T, ndims(a), typeof(a.parent), typeof(newop)}( + a.parent, a.size, a.strides, a.offset, newop + ) end # Creating or transforming StridedView by slicing @@ -251,6 +279,7 @@ end # we cannot use Base.reshape, as this also accepts indices that might not preserve stridedness sreshape(a, args::Vararg{Int}) = sreshape(a, args) function sreshape(a::StridedView{T}, newsize::Dims) where {T} + size(a) == newsize && return a if any(isequal(0), newsize) any(isequal(0), size(a)) || throw(DimensionMismatch()) newstrides = one.(newsize) @@ -258,7 +287,9 @@ function sreshape(a::StridedView{T}, newsize::Dims) where {T} newstrides = _computereshapestrides(newsize, _simplifydims(size(a), strides(a))...) end isnothing(newstrides) && throw(ReshapeException(newsize, size(a), strides(a))) - return StridedView{T}(a.parent, newsize, newstrides, a.offset, a.op) + return StridedView{T, length(newsize), typeof(a.parent), typeof(a.op)}( + a.parent, newsize, _normalizestrides(newsize, newstrides), a.offset, a.op + ) end sreshape(a::AbstractArray, newsize::Dims) = sreshape(StridedView(a), newsize) diff --git a/test/runtests.jl b/test/runtests.jl index 1f7aa8b..421a641 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -222,14 +222,20 @@ if !is_buildkite @testset "more reshape" begin A = randn(4, 0) B = StridedView(A) + @test sreshape(B, size(B)) === B @test_throws DimensionMismatch sreshape(B, (4, 1)) C = sreshape(B, (2, 1, 2, 0, 1)) + @test strides(C) == (1, 2, 2, 4, 0) @test sreshape(C, (4, 0)) == A A = randn(4, 1, 2) B = StridedView(A) @test_throws DimensionMismatch sreshape(B, (4, 4)) + @test sreshape(B, size(B)) === B + @test strides(permutedims(B, (2, 1, 3))) == (1, 1, 4) + @test strides(B[1:2:3, :, :]) == (2, 4, 4) C = sreshape(B, (2, 1, 1, 4, 1, 1)) + @test strides(C) == (1, 2, 2, 2, 8, 8) @test C == reshape(A, (2, 1, 1, 4, 1, 1)) @test sreshape(C, (4, 1, 2)) == A end From b003f645837f7cf5916a84c5fbdb0fe78db52966 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Thu, 8 Oct 2026 11:37:46 -0400 Subject: [PATCH 2/2] Add `normalize` keyword to `StridedView` constructors Instead of calling the fully parameterized inner constructor at every call site that reuses an already-normalized parent, let the outer `StridedView`/`StridedView{T}` constructors take `normalize = false` to skip parent and stride normalization. Co-Authored-By: Claude Opus 5.5 --- src/stridedview.jl | 66 ++++++++++++++++++++++++++++------------------ test/runtests.jl | 3 +++ 2 files changed, 44 insertions(+), 25 deletions(-) diff --git a/src/stridedview.jl b/src/stridedview.jl index e9f6099..ac5dea3 100644 --- a/src/stridedview.jl +++ b/src/stridedview.jl @@ -39,18 +39,23 @@ function StridedView( size::NTuple{N, Int} = size(parent), strides::NTuple{N, Int} = strides(parent), offset::Int = 0, - op::F = identity + op::F = identity; + normalize::Bool = true ) where {N, F} T = Base.promote_op(op, eltype(parent)) - return StridedView{T}(parent, size, strides, offset, op) + return StridedView{T}(parent, size, strides, offset, op; normalize) end function StridedView{T}( parent::DenseArray, size::NTuple{N, Int} = size(parent), strides::NTuple{N, Int} = strides(parent), offset::Int = 0, - op::F = identity + op::F = identity; + normalize::Bool = true ) where {T, N, F} + # `normalize = false` asserts that `parent` and `strides` are already normalized, + # e.g. because they are taken from an existing `StridedView`. + normalize || return StridedView{T, N, typeof(parent), F}(parent, size, strides, offset, op) parent′ = _normalizeparent(parent) strides′ = _normalizestrides(size, strides) return StridedView{T, N, typeof(parent′), F}(parent′, size, strides′, offset, op) @@ -71,8 +76,9 @@ function StridedView(a::Base.ReinterpretArray{T, N}) where {T, N} throw(ArgumentError("Cannot create StridedView with reinterpretation from $S to $T")) b.op isa FN || throw(ArgumentError("Cannot create StridedView with reinterpretation from view with non-identity operation")) - return StridedView{T, ndims(b), typeof(b.parent), FN}( - b.parent, size(b), strides(b), offset(b), identity + return StridedView{T}( + b.parent, size(b), strides(b), offset(b), identity; + normalize = false ) end @@ -144,12 +150,13 @@ end function Base.getindex(a::StridedView{T, N}, I::Vararg{SliceIndex, N}) where {T, N} newsize = _computeviewsize(a.size, I) newstrides = _normalizestrides(newsize, _computeviewstrides(a.strides, I)) - return StridedView{T, length(newsize), typeof(a.parent), typeof(a.op)}( + return StridedView{T}( a.parent, newsize, newstrides, a.offset + _computeviewoffset(a.strides, I), - a.op + a.op; + normalize = false ) end @@ -175,16 +182,18 @@ end Base.conj(a::StridedView{<:Real}) = a function Base.conj(a::StridedView{T}) where {T <: Complex} newop = _conj(a.op) - return StridedView{T, ndims(a), typeof(a.parent), typeof(newop)}( - a.parent, a.size, a.strides, a.offset, newop + return StridedView{T}( + a.parent, a.size, a.strides, a.offset, newop; + normalize = false ) end function Base.conj(a::StridedView) S = Base.promote_op(a.op, eltype(a)) newop = _conj(a.op) T = Base.promote_op(newop, S) - return StridedView{T, ndims(a), typeof(a.parent), typeof(newop)}( - a.parent, a.size, a.strides, a.offset, newop + return StridedView{T}( + a.parent, a.size, a.strides, a.offset, newop; + normalize = false ) end @@ -192,8 +201,9 @@ function Base.permutedims(a::StridedView{T, N}, p) where {T, N} _isperm(N, p) || throw(ArgumentError("Invalid permutation of length $N: $p")) newsize = ntuple(n -> size(a, p[n]), Val(N)) newstrides = ntuple(n -> stride(a, p[n]), Val(N)) - return typeof(a)( - a.parent, newsize, _normalizestrides(newsize, newstrides), a.offset, a.op + return StridedView{T}( + a.parent, newsize, _normalizestrides(newsize, newstrides), a.offset, a.op; + normalize = false ) end @@ -204,8 +214,9 @@ function LinearAlgebra.adjoint(a::StridedView{<:Any, 2}) # act recursively, like newop = _adjoint(a.op) T = Base.promote_op(newop, S) return permutedims( - StridedView{T, ndims(a), typeof(a.parent), typeof(newop)}( - a.parent, a.size, a.strides, a.offset, newop + StridedView{T}( + a.parent, a.size, a.strides, a.offset, newop; + normalize = false ), (2, 1) ) end @@ -214,8 +225,9 @@ function LinearAlgebra.transpose(a::StridedView{<:Any, 2}) # act recursively, li newop = _transpose(a.op) T = Base.promote_op(newop, S) return permutedims( - StridedView{T, ndims(a), typeof(a.parent), typeof(newop)}( - a.parent, a.size, a.strides, a.offset, newop + StridedView{T}( + a.parent, a.size, a.strides, a.offset, newop; + normalize = false ), (2, 1) ) end @@ -226,22 +238,25 @@ Base.map(::FA, a::StridedView{<:Number}) = conj(a) function Base.map(::FC, a::StridedView) T = Base.promote_op(conj, eltype(a)) newop = _conj(a.op) - return StridedView{T, ndims(a), typeof(a.parent), typeof(newop)}( - a.parent, a.size, a.strides, a.offset, newop + return StridedView{T}( + a.parent, a.size, a.strides, a.offset, newop; + normalize = false ) end function Base.map(::FT, a::StridedView) T = Base.promote_op(transpose, eltype(a)) newop = _transpose(a.op) - return StridedView{T, ndims(a), typeof(a.parent), typeof(newop)}( - a.parent, a.size, a.strides, a.offset, newop + return StridedView{T}( + a.parent, a.size, a.strides, a.offset, newop; + normalize = false ) end function Base.map(::FA, a::StridedView) T = Base.promote_op(adjoint, eltype(a)) newop = _adjoint(a.op) - return StridedView{T, ndims(a), typeof(a.parent), typeof(newop)}( - a.parent, a.size, a.strides, a.offset, newop + return StridedView{T}( + a.parent, a.size, a.strides, a.offset, newop; + normalize = false ) end @@ -287,8 +302,9 @@ function sreshape(a::StridedView{T}, newsize::Dims) where {T} newstrides = _computereshapestrides(newsize, _simplifydims(size(a), strides(a))...) end isnothing(newstrides) && throw(ReshapeException(newsize, size(a), strides(a))) - return StridedView{T, length(newsize), typeof(a.parent), typeof(a.op)}( - a.parent, newsize, _normalizestrides(newsize, newstrides), a.offset, a.op + return StridedView{T}( + a.parent, newsize, _normalizestrides(newsize, newstrides), a.offset, a.op; + normalize = false ) end diff --git a/test/runtests.jl b/test/runtests.jl index 421a641..778f16a 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -234,6 +234,9 @@ if !is_buildkite @test sreshape(B, size(B)) === B @test strides(permutedims(B, (2, 1, 3))) == (1, 1, 4) @test strides(B[1:2:3, :, :]) == (2, 4, 4) + @test StridedView( + B.parent, size(B), strides(B), B.offset, B.op; normalize = false + ) === B C = sreshape(B, (2, 1, 1, 4, 1, 1)) @test strides(C) == (1, 2, 2, 2, 8, 8) @test C == reshape(A, (2, 1, 1, 4, 1, 1))