Skip to content

Commit c14fbe8

Browse files
authored
support GenericMemory by making the storage type a parameter (#52)
* support `GenericMemory` by making the storage type a parameter This approach isolates us from possible changes in the `GenericMemory` design while leaving open the possibility of supporting other underlying storage types in the future. Fixes #33 * fix tests 1 * fix tests 2 * fix tests 3 * delete todo comment suggesting Preferences.jl * test: check if the compilation options allow maximum performance * adjust Github action
1 parent 03117ab commit c14fbe8

File tree

3 files changed

+526
-384
lines changed

3 files changed

+526
-384
lines changed

.github/workflows/UnitTests.yml

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,34 @@ jobs:
2929
- ubuntu-latest
3030
- macos-latest
3131
- windows-latest
32-
32+
build_is_production_build:
33+
- true
34+
runs-on: ${{ matrix.os }}
35+
steps:
36+
- uses: actions/checkout@v4
37+
- uses: julia-actions/setup-julia@v2
38+
with:
39+
arch: ${{ matrix.julia_arch }}
40+
version: ${{ matrix.julia_version }}
41+
- uses: julia-actions/cache@v2
42+
- uses: julia-actions/julia-runtest@v1
43+
env:
44+
BUILD_IS_PRODUCTION_BUILD: ${{ matrix.build_is_production_build }}
45+
with:
46+
coverage: false
47+
test-with-code-coverage:
48+
name: Julia ${{ matrix.julia_version }} - ${{ matrix.os }} - ${{ matrix.julia_arch }} - with code coverage
49+
timeout-minutes: 20
50+
strategy:
51+
fail-fast: false
52+
matrix:
53+
julia_version:
54+
- "~1.11.0-0"
55+
julia_arch:
56+
- x64
57+
os:
58+
- ubuntu-latest
3359
runs-on: ${{ matrix.os }}
34-
3560
steps:
3661
- uses: actions/checkout@v4
3762
- uses: julia-actions/setup-julia@v2
@@ -40,6 +65,8 @@ jobs:
4065
version: ${{ matrix.julia_version }}
4166
- uses: julia-actions/cache@v2
4267
- uses: julia-actions/julia-runtest@v1
68+
env:
69+
BUILD_IS_PRODUCTION_BUILD: false
4370
- uses: julia-actions/julia-processcoverage@v1
4471
- uses: codecov/codecov-action@v4
4572
with:

src/FixedSizeArrays.jl

Lines changed: 127 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -11,23 +11,37 @@ Implementation detail. Do not use.
1111
"""
1212
struct Internal end
1313

14-
struct FixedSizeArray{T,N} <: DenseArray{T,N}
15-
mem::Memory{T}
14+
struct FixedSizeArray{T,N,Mem<:GenericMemory{<:Any,T}} <: DenseArray{T,N}
15+
mem::Mem
1616
size::NTuple{N,Int}
17-
function FixedSizeArray{T,N}(::Internal, mem::Memory{T}, size::NTuple{N,Int}) where {T,N}
18-
new{T,N}(mem, size)
17+
function FixedSizeArray{T,N,M}(::Internal, mem::M, size::NTuple{N,Int}) where {T,N,M<:GenericMemory{<:Any,T}}
18+
new{T,N,M}(mem, size)
1919
end
2020
end
2121

2222
const FixedSizeVector{T} = FixedSizeArray{T,1}
2323
const FixedSizeMatrix{T} = FixedSizeArray{T,2}
2424

25-
function FixedSizeArray{T,N}(::UndefInitializer, size::NTuple{N,Int}) where {T,N}
26-
FixedSizeArray{T,N}(Internal(), Memory{T}(undef, checked_dims(size)), size)
25+
const default_underlying_storage_type = Memory
26+
27+
function FixedSizeArray{T,N,V}(::UndefInitializer, size::NTuple{N,Int}) where {T,N,V}
28+
FixedSizeArray{T,N,V}(Internal(), V(undef, checked_dims(size))::V, size)
2729
end
28-
function FixedSizeArray{T,N}(::UndefInitializer, size::NTuple{N,Integer}) where {T,N}
30+
function FixedSizeArray{T,N,V}(::UndefInitializer, size::NTuple{N,Integer}) where {T,N,V}
2931
ints = map(Int, size)::NTuple{N,Int} # prevent infinite recursion
30-
FixedSizeArray{T,N}(undef, ints)
32+
FixedSizeArray{T,N,V}(undef, ints)
33+
end
34+
function FixedSizeArray{T,N,V}(::UndefInitializer, size::Vararg{Integer,N}) where {T,N,V}
35+
FixedSizeArray{T,N,V}(undef, size)
36+
end
37+
function FixedSizeArray{T,<:Any,V}(::UndefInitializer, size::NTuple{N,Integer}) where {T,N,V}
38+
FixedSizeArray{T,N,V}(undef, size)
39+
end
40+
function FixedSizeArray{T,<:Any,V}(::UndefInitializer, size::Vararg{Integer,N}) where {T,N,V}
41+
FixedSizeArray{T,N,V}(undef, size)
42+
end
43+
function FixedSizeArray{T,N}(::UndefInitializer, size::NTuple{N,Integer}) where {T,N}
44+
FixedSizeArray{T,N,default_underlying_storage_type{T}}(undef, size)
3145
end
3246
function FixedSizeArray{T,N}(::UndefInitializer, size::Vararg{Integer,N}) where {T,N}
3347
FixedSizeArray{T,N}(undef, size)
@@ -45,8 +59,8 @@ Base.@propagate_inbounds Base.setindex!(A::FixedSizeArray, v, i::Int) = A.mem[i]
4559

4660
Base.size(a::FixedSizeArray) = a.size
4761

48-
function Base.similar(::FixedSizeArray, ::Type{S}, size::NTuple{N,Int}) where {S,N}
49-
FixedSizeArray{S,N}(undef, size)
62+
function Base.similar(::T, ::Type{E}, size::NTuple{N,Int}) where {T<:FixedSizeArray,E,N}
63+
with_replaced_parameters(DenseArray, T, Val(E), Val(N))(undef, size)
5064
end
5165

5266
Base.isassigned(a::FixedSizeArray, i::Int) = isassigned(a.mem, i)
@@ -83,19 +97,45 @@ end
8397

8498
# broadcasting
8599

86-
function Base.BroadcastStyle(::Type{<:FixedSizeArray})
87-
Broadcast.ArrayStyle{FixedSizeArray}()
100+
function Base.BroadcastStyle(::Type{T}) where {T<:FixedSizeArray}
101+
Broadcast.ArrayStyle{stripped_type(DenseArray, T)}()
88102
end
89103

90104
function Base.similar(
91-
bc::Broadcast.Broadcasted{Broadcast.ArrayStyle{FixedSizeArray}},
105+
bc::Broadcast.Broadcasted{Broadcast.ArrayStyle{S}},
92106
::Type{E},
93-
) where {E}
94-
similar(FixedSizeArray{E}, axes(bc))
107+
) where {S<:FixedSizeArray,E}
108+
similar(S{E}, axes(bc))
95109
end
96110

97111
# helper functions
98112

113+
normalized_type(::Type{T}) where {T} = T
114+
115+
function stripped_type_unchecked(::Type{DenseVector}, ::Type{<:GenericMemory{K,<:Any,AS}}) where {K,AS}
116+
GenericMemory{K,<:Any,AS}
117+
end
118+
119+
Base.@assume_effects :consistent function stripped_type_unchecked(
120+
::Type{DenseArray}, ::Type{<:FixedSizeArray{<:Any,<:Any,V}},
121+
) where {V}
122+
U = stripped_type(DenseVector, V)
123+
FixedSizeArray{E,N,U{E}} where {E,N}
124+
end
125+
126+
function stripped_type(::Type{T}, ::Type{S}) where {T,S<:T}
127+
ret = stripped_type_unchecked(T, S)::Type{<:T}::UnionAll
128+
S::Type{<:ret}
129+
normalized_type(ret) # ensure `UnionAll` type variable order is normalized
130+
end
131+
132+
function with_replaced_parameters(::Type{T}, ::Type{S}, ::Val{P1}, ::Val{P2}) where {T,S<:T,P1,P2}
133+
t = T{P1,P2}::Type{<:T}
134+
s = stripped_type(T, S)
135+
S::Type{<:s}
136+
s{P1,P2}::Type{<:s}::Type{<:T}::Type{<:t}
137+
end
138+
99139
dimension_count_of(::Base.SizeUnknown) = 1
100140
dimension_count_of(::Base.HasLength) = 1
101141
dimension_count_of(::Base.HasShape{N}) where {N} = convert(Int, N)::Int
@@ -115,23 +155,49 @@ function check_count_value(n)
115155
throw(ArgumentError("count must be an `Int`"))
116156
end
117157

118-
struct SpecFSA{T,N} end
158+
# TODO: use `SpecFSA` for implementing each `FixedSizeArray` constructor?
159+
struct SpecFSA{N,Mem<:GenericMemory} end
119160
function fsa_spec_from_type(::Type{FixedSizeArray})
120-
SpecFSA{nothing,nothing}()
161+
SpecFSA{nothing,default_underlying_storage_type}()
121162
end
122163
function fsa_spec_from_type(::Type{FixedSizeArray{<:Any,M}}) where {M}
123164
check_count_value(M)
124-
SpecFSA{nothing,M}()
165+
SpecFSA{M,default_underlying_storage_type}()
125166
end
126167
function fsa_spec_from_type(::Type{FixedSizeArray{E}}) where {E}
127-
SpecFSA{E::Type,nothing}()
168+
E::Type
169+
SpecFSA{nothing,default_underlying_storage_type{E}}()
128170
end
129171
function fsa_spec_from_type(::Type{FixedSizeArray{E,M}}) where {E,M}
130172
check_count_value(M)
131-
SpecFSA{E::Type,M}()
173+
E::Type
174+
SpecFSA{M,default_underlying_storage_type{E}}()
175+
end
176+
function fsa_spec_from_type(::Type{FixedSizeArray{E,<:Any,V}}) where {E,V}
177+
E::Type
178+
V::Type{<:DenseVector{E}}
179+
SpecFSA{nothing,V}()
180+
end
181+
function fsa_spec_from_type(::Type{FixedSizeArray{E,M,V}}) where {E,M,V}
182+
check_count_value(M)
183+
E::Type
184+
V::Type{<:DenseVector{E}}
185+
SpecFSA{M,V}()
186+
end
187+
for V (Memory, AtomicMemory)
188+
T = FixedSizeArray{E,M,V{E}} where {E,M}
189+
@eval begin
190+
function fsa_spec_from_type(::Type{$T})
191+
SpecFSA{nothing,$V}()
192+
end
193+
function fsa_spec_from_type(::Type{($T){<:Any,M}}) where {M}
194+
check_count_value(M)
195+
SpecFSA{M,$V}()
196+
end
197+
end
132198
end
133199

134-
parent_type(::Type{<:FixedSizeArray{T}}) where {T} = Memory{T}
200+
parent_type(::Type{<:FixedSizeArray{<:Any,<:Any,T}}) where {T} = T
135201

136202
underlying_storage(m) = m
137203
underlying_storage(f::FixedSizeArray) = f.mem
@@ -140,19 +206,22 @@ axes_are_one_based(axes) = all(isone ∘ first, axes)
140206

141207
# converting constructors for copying other array types
142208

143-
function FixedSizeArray{T,N}(src::AbstractArray{S,N}) where {T,N,S}
209+
function FixedSizeArray{T,N,V}(src::AbstractArray{S,N}) where {T,N,V,S}
144210
axs = axes(src)
145211
if !axes_are_one_based(axs)
146212
throw(DimensionMismatch("source array has a non-one-based indexing axis"))
147213
end
148214
# Can't use `Base.size` because, according to it's doc string, it's not
149215
# available for all `AbstractArray` types.
150216
size = map(length, axs)
151-
dst = FixedSizeArray{T,N}(undef, size)
217+
dst = FixedSizeArray{T,N,V}(undef, size)
152218
copyto!(dst.mem, src)
153219
dst
154220
end
155221

222+
FixedSizeArray{T,<:Any,V}(a::AbstractArray{<:Any,N}) where {V,T,N} = FixedSizeArray{T,N,V}(a)
223+
224+
FixedSizeArray{T,N}(a::AbstractArray{<:Any,N}) where {T,N} = FixedSizeArray{T,N,default_underlying_storage_type{T}}(a)
156225
FixedSizeArray{T}(a::AbstractArray{<:Any,N}) where {T,N} = FixedSizeArray{T,N}(a)
157226
FixedSizeArray{<:Any,N}(a::AbstractArray{T,N}) where {T,N} = FixedSizeArray{T,N}(a)
158227
FixedSizeArray(a::AbstractArray{T,N}) where {T,N} = FixedSizeArray{T,N}(a)
@@ -197,27 +266,29 @@ Base.elsize(::Type{A}) where {A<:FixedSizeArray} = Base.elsize(parent_type(A))
197266

198267
# `reshape`: specializing it to ensure it returns a `FixedSizeArray`
199268

200-
function Base.reshape(a::FixedSizeArray{T}, size::NTuple{N,Int}) where {T,N}
269+
function Base.reshape(a::FixedSizeArray{T,<:Any,V}, size::NTuple{N,Int}) where {V,T,N}
201270
len = checked_dims(size)
202271
if length(a) != len
203272
throw(DimensionMismatch("new shape not consistent with existing array length"))
204273
end
205-
FixedSizeArray{T,N}(Internal(), a.mem, size)
274+
FixedSizeArray{T,N,V}(Internal(), a.mem, size)
206275
end
207276

208277
# `collect_as`
209278

210-
function collect_as_fsa0(iterator, ::Val{nothing})
279+
function collect_as_fsa0(iterator, ::SpecFSA{0,V}) where {V}
280+
V::UnionAll
211281
x = only(iterator)
212-
ret = FixedSizeArray{typeof(x),0}(undef)
282+
E = typeof(x)::Type
283+
ret = FixedSizeArray{E,0,V{E}}(undef)
213284
ret[] = x
214285
ret
215286
end
216287

217-
function collect_as_fsa0(iterator, ::Val{E}) where {E}
288+
function collect_as_fsa0(iterator, ::SpecFSA{0,V}) where {E,V<:DenseVector{E}}
218289
E::Type
219290
x = only(iterator)
220-
ret = FixedSizeArray{E,0}(undef)
291+
ret = FixedSizeArray{E,0,V}(undef)
221292
ret[] = x
222293
ret
223294
end
@@ -234,24 +305,26 @@ function fill_fsa_from_iterator!(a, iterator)
234305
end
235306

236307
function collect_as_fsam_with_shape(
237-
iterator, ::SpecFSA{nothing,M}, shape::Tuple{Vararg{Int}},
238-
) where {M}
308+
iterator, ::SpecFSA{M,V}, shape::Tuple{Vararg{Int}},
309+
) where {M,V}
310+
V::UnionAll
239311
E = eltype(iterator)::Type
240-
ret = FixedSizeArray{E,M}(undef, shape)
312+
U = V{E}
313+
ret = FixedSizeArray{E,M,U}(undef, shape)
241314
fill_fsa_from_iterator!(ret, iterator)
242-
map(identity, ret)::FixedSizeArray{<:Any,M}
315+
map(identity, ret)::(FixedSizeArray{T,M,V{T}} where {T})
243316
end
244317

245318
function collect_as_fsam_with_shape(
246-
iterator, ::SpecFSA{E,M}, shape::Tuple{Vararg{Int}},
247-
) where {E,M}
319+
iterator, ::SpecFSA{M,V}, shape::Tuple{Vararg{Int}},
320+
) where {M,E,V<:DenseVector{E}}
248321
E::Type
249-
ret = FixedSizeArray{E,M}(undef, shape)
322+
ret = FixedSizeArray{E,M,V}(undef, shape)
250323
fill_fsa_from_iterator!(ret, iterator)
251-
ret::FixedSizeArray{E,M}
324+
ret::FixedSizeArray{E,M,V}
252325
end
253326

254-
function collect_as_fsam(iterator, spec::SpecFSA{<:Any,M}) where {M}
327+
function collect_as_fsam(iterator, spec::SpecFSA{M}) where {M}
255328
check_count_value(M)
256329
shape = if isone(M)
257330
(length(iterator),)
@@ -262,39 +335,42 @@ function collect_as_fsam(iterator, spec::SpecFSA{<:Any,M}) where {M}
262335
collect_as_fsam_with_shape(iterator, spec, shap)::FixedSizeArray{<:Any,M}
263336
end
264337

265-
function collect_as_fsa1_from_unknown_length(iterator, ::Val{nothing})
338+
function collect_as_fsa1_from_unknown_length(iterator, ::SpecFSA{1,V}) where {V}
339+
V::UnionAll
266340
v = collect(iterator)::AbstractVector
267-
T = FixedSizeVector
268-
map(identity, T(v))::T
341+
t = FixedSizeVector(v)::FixedSizeVector
342+
s = map(identity, t)::FixedSizeVector # fix element type
343+
et = eltype(s)
344+
FixedSizeVector{et,V{et}}(s) # fix underlying storage type
269345
end
270346

271-
function collect_as_fsa1_from_unknown_length(iterator, ::Val{E}) where {E}
347+
function collect_as_fsa1_from_unknown_length(iterator, ::SpecFSA{1,V}) where {E,V<:DenseVector{E}}
272348
E::Type
273349
v = collect(E, iterator)::AbstractVector{E}
274-
T = FixedSizeVector{E}
350+
T = FixedSizeVector{E,V}
275351
T(v)::T
276352
end
277353

278-
function collect_as_fsa_impl(iterator, ::SpecFSA{E,0}, ::LengthIsKnown) where {E}
279-
collect_as_fsa0(iterator, Val(E))::FixedSizeArray{<:Any,0}
354+
function collect_as_fsa_impl(iterator, spec::SpecFSA{0}, ::LengthIsKnown)
355+
collect_as_fsa0(iterator, spec)::FixedSizeArray{<:Any,0}
280356
end
281357

282358
function collect_as_fsa_impl(iterator, spec::SpecFSA, ::LengthIsKnown)
283359
collect_as_fsam(iterator, spec)::FixedSizeArray
284360
end
285361

286-
function collect_as_fsa_impl(iterator, ::SpecFSA{E,1}, ::LengthIsUnknown) where {E}
287-
collect_as_fsa1_from_unknown_length(iterator, Val(E))::FixedSizeVector
362+
function collect_as_fsa_impl(iterator, spec::SpecFSA{1}, ::LengthIsUnknown)
363+
collect_as_fsa1_from_unknown_length(iterator, spec)::FixedSizeVector
288364
end
289365

290-
function collect_as_fsa_checked(iterator, ::SpecFSA{E,nothing}, ::Val{M}, length_status) where {E,M}
366+
function collect_as_fsa_checked(iterator, ::SpecFSA{nothing,V}, ::Val{M}, length_status) where {V,M}
291367
check_count_value(M)
292-
collect_as_fsa_impl(iterator, SpecFSA{E,M}(), length_status)::FixedSizeArray{<:Any,M}
368+
collect_as_fsa_impl(iterator, SpecFSA{M,V}(), length_status)::FixedSizeArray{<:Any,M}
293369
end
294370

295-
function collect_as_fsa_checked(iterator, ::SpecFSA{E,M}, ::Val{M}, length_status) where {E,M}
371+
function collect_as_fsa_checked(iterator, spec::SpecFSA{M}, ::Val{M}, length_status) where {M}
296372
check_count_value(M)
297-
collect_as_fsa_impl(iterator, SpecFSA{E,M}(), length_status)::FixedSizeArray{<:Any,M}
373+
collect_as_fsa_impl(iterator, spec, length_status)::FixedSizeArray{<:Any,M}
298374
end
299375

300376
"""

0 commit comments

Comments
 (0)