diff --git a/ext/AdaptiveArrayPoolsCUDAExt/AdaptiveArrayPoolsCUDAExt.jl b/ext/AdaptiveArrayPoolsCUDAExt/AdaptiveArrayPoolsCUDAExt.jl index 238b0e2..bef3f3a 100644 --- a/ext/AdaptiveArrayPoolsCUDAExt/AdaptiveArrayPoolsCUDAExt.jl +++ b/ext/AdaptiveArrayPoolsCUDAExt/AdaptiveArrayPoolsCUDAExt.jl @@ -9,7 +9,20 @@ Loaded automatically when `using CUDA` with AdaptiveArrayPools. module AdaptiveArrayPoolsCUDAExt using AdaptiveArrayPools -using AdaptiveArrayPools: AbstractTypedPool, AbstractArrayPool, CACHE_WAYS +using AdaptiveArrayPools: AbstractTypedPool, AbstractArrayPool +using Preferences: @load_preference, @set_preferences! + +# N-way view cache configuration (CUDA only — CPU ≥1.11 uses slot-first _claim_slot!). +# GPU view/reshape allocates ~80 bytes on CPU heap, so caching still matters. +const CACHE_WAYS = let + ways = @load_preference("cache_ways", 4)::Int + if ways < 1 || ways > 16 + @warn "CACHE_WAYS=$ways out of range [1,16], using default 4" + 4 + else + ways + end +end using CUDA # Type definitions diff --git a/ext/AdaptiveArrayPoolsCUDAExt/acquire.jl b/ext/AdaptiveArrayPoolsCUDAExt/acquire.jl index 272fcf0..e6149ca 100644 --- a/ext/AdaptiveArrayPoolsCUDAExt/acquire.jl +++ b/ext/AdaptiveArrayPoolsCUDAExt/acquire.jl @@ -27,7 +27,7 @@ # - Could track recent N sizes to make smarter decisions (avoid shrink if sizes fluctuate) # ============================================================================== -using AdaptiveArrayPools: get_view!, get_nd_view!, get_nd_array!, allocate_vector, safe_prod, +using AdaptiveArrayPools: get_view!, get_array!, allocate_vector, safe_prod, _record_type_touch!, _fixed_slot_bit, _checkpoint_typed_pool!, _MODE_BITS_MASK @@ -138,30 +138,16 @@ See module header for "lazy shrink" optimization notes. end # ============================================================================== -# CUDA-Specific get_nd_view! - Delegates to unified get_view! +# CUDA-Specific get_array! - Delegates to unified get_view! # ============================================================================== """ - get_nd_view!(tp::CuTypedPool{T}, dims::NTuple{N,Int}) -> CuArray{T,N} - -Delegates to `get_view!(tp, dims)` for unified caching. -This override exists for API compatibility with the base package. -""" -@inline function AdaptiveArrayPools.get_nd_view!(tp::CuTypedPool{T}, dims::NTuple{N, Int}) where {T, N} - return get_view!(tp, dims) -end - -# ============================================================================== -# CUDA-Specific get_nd_array! - Delegates to unified get_view! -# ============================================================================== - -""" - get_nd_array!(tp::CuTypedPool{T}, dims::NTuple{N,Int}) -> CuArray{T,N} + get_array!(tp::CuTypedPool{T}, dims::NTuple{N,Int}) -> CuArray{T,N} Delegates to `get_view!(tp, dims)` for unified caching. Used by `unsafe_acquire!` - same zero-allocation behavior as `acquire!`. """ -@inline function AdaptiveArrayPools.get_nd_array!(tp::CuTypedPool{T}, dims::NTuple{N, Int}) where {T, N} +@inline function AdaptiveArrayPools.get_array!(tp::CuTypedPool{T}, dims::NTuple{N, Int}) where {T, N} return get_view!(tp, dims) end diff --git a/ext/AdaptiveArrayPoolsCUDAExt/dispatch.jl b/ext/AdaptiveArrayPoolsCUDAExt/dispatch.jl index f5ff39a..5dac0a3 100644 --- a/ext/AdaptiveArrayPoolsCUDAExt/dispatch.jl +++ b/ext/AdaptiveArrayPoolsCUDAExt/dispatch.jl @@ -3,7 +3,7 @@ # ============================================================================== # Key dispatch points for GPU-specific allocation and type routing. -using AdaptiveArrayPools: allocate_vector, wrap_array, get_typed_pool! +using AdaptiveArrayPools: allocate_vector, get_typed_pool! # ============================================================================== # Allocation Dispatch (single GPU-specific method needed!) @@ -13,16 +13,6 @@ using AdaptiveArrayPools: allocate_vector, wrap_array, get_typed_pool! ::AbstractTypedPool{T, CuVector{T}}, n::Int ) where {T} = CuVector{T}(undef, n) -# ============================================================================== -# Array Wrapping Dispatch -# ============================================================================== - -# GPU uses reshape which returns CuArray{T,N} via GPUArrays derive() -# (NOT ReshapedArray like CPU - this is simpler for GPU kernels) -@inline AdaptiveArrayPools.wrap_array( - ::AbstractTypedPool{T, CuVector{T}}, flat_view, dims::NTuple{N, Int} -) where {T, N} = reshape(flat_view, dims) - # ============================================================================== # get_typed_pool! Dispatches for CuAdaptiveArrayPool # ============================================================================== diff --git a/src/AdaptiveArrayPools.jl b/src/AdaptiveArrayPools.jl index 2cb226d..2a68ce5 100644 --- a/src/AdaptiveArrayPools.jl +++ b/src/AdaptiveArrayPools.jl @@ -11,7 +11,6 @@ export Bit # Sentinel type for BitArray (use with acquire!, trues!, falses!) export @with_pool, @maybe_with_pool export USE_POOLING, MAYBE_POOLING_ENABLED, POOL_DEBUG export checkpoint!, rewind!, reset! -export CACHE_WAYS, set_cache_ways! # N-way cache configuration export get_task_local_cuda_pool, get_task_local_cuda_pools # CUDA (stubs, overridden by extension) # Extension API (for GPU backends) @@ -30,6 +29,7 @@ export DisabledPool, DISABLED_CPU, pooling_enabled # Disabled pool support include("task_local_pool.jl") include("macros.jl") else + export CACHE_WAYS, set_cache_ways! # N-way cache configuration (legacy only) include("legacy/types.jl") include("utils.jl") include("legacy/acquire.jl") diff --git a/src/acquire.jl b/src/acquire.jl index f1dbdeb..3bd30bb 100644 --- a/src/acquire.jl +++ b/src/acquire.jl @@ -6,14 +6,6 @@ @inline allocate_vector(::AbstractTypedPool{T, Vector{T}}, n::Int) where {T} = Vector{T}(undef, n) -# Wrap flat view into N-D array (dispatch point for extensions) -@inline function wrap_array( - ::AbstractTypedPool{T, Vector{T}}, - flat_view, dims::NTuple{N, Int} - ) where {T, N} - return unsafe_wrap(Array{T, N}, pointer(flat_view), dims) -end - # ============================================================================== # Helper: Overflow-Safe Product # ============================================================================== @@ -61,73 +53,78 @@ end end # ============================================================================== -# Get 1D View (Internal - Zero-Allocation Cache) +# Slot Claim — Shared Primitive for All Acquisition Paths # ============================================================================== """ - get_view!(tp::AbstractTypedPool{T}, n::Int) + _claim_slot!(tp::TypedPool{T}, n::Int) -> Int -Get a 1D vector view of size `n` from the typed pool. -Returns cached view on hit (zero allocation), creates new on miss. +Claim the next slot, ensuring the backing vector exists and has capacity >= `n`. +Returns the slot index. This is the shared primitive for all acquisition paths +(`get_view!`, `get_array!`). """ -function get_view!(tp::AbstractTypedPool{T}, n::Int) where {T} +@inline function _claim_slot!(tp::TypedPool{T}, n::Int) where {T} tp.n_active += 1 idx = tp.n_active - - # 1. Need to expand pool (new slot) if idx > length(tp.vectors) push!(tp.vectors, allocate_vector(tp, n)) - new_view = view(tp.vectors[idx], 1:n) - push!(tp.views, new_view) - push!(tp.view_lengths, n) _check_pool_growth(tp, idx) - - return new_view - end - - # 2. Cache hit: same size requested -> return cached view (ZERO ALLOC) - @inbounds cached_len = tp.view_lengths[idx] - if cached_len == n - return @inbounds tp.views[idx] - end - - # 3. Cache miss: different size -> update cache - @inbounds vec = tp.vectors[idx] - if length(vec) < n - resize!(vec, n) + else + @inbounds vec = tp.vectors[idx] + if length(vec) < n + resize!(vec, n) + end end - - new_view = view(vec, 1:n) - @inbounds tp.views[idx] = new_view - @inbounds tp.view_lengths[idx] = n - - return new_view + return idx end -# ============================================================================== -# Slot Claim (for reshape! — wrapper-only, no backing memory) -# ============================================================================== - """ _claim_slot!(tp::TypedPool{T}) -> Int -Claim the next slot index by incrementing `n_active`. -Ensures the slot exists in vectors/views/view_lengths arrays. -The backing vector at this slot is unused — this is for wrapper-only caching -(e.g., `reshape!` uses the slot index for `nd_wrapper` storage only). +Claim the next slot without provisioning memory (zero-length backing vector). +Used by `reshape!` which only needs the slot index for `nd_wrapper` caching — +the wrapper points to a different array's memory via `setfield!(:ref)`. """ @inline function _claim_slot!(tp::TypedPool{T}) where {T} tp.n_active += 1 idx = tp.n_active if idx > length(tp.vectors) push!(tp.vectors, Vector{T}(undef, 0)) - push!(tp.views, view(tp.vectors[idx], 1:0)) - push!(tp.view_lengths, 0) _check_pool_growth(tp, idx) end return idx end +# ============================================================================== +# Get View (Internal — Always Fresh, SubArray is Stack-Allocated via SROA) +# ============================================================================== + +""" + get_view!(tp::TypedPool{T}, n::Int) -> SubArray{T,1} + get_view!(tp::TypedPool{T}, dims::NTuple{N,Int}) -> ReshapedArray{T,N} + +Get a pooled view from the typed pool. +- **1D**: Returns a fresh `SubArray` (stack-allocated via SROA in compiled code). +- **N-D**: Returns a `ReshapedArray` wrapping a 1D view (zero creation cost). + +Always creates fresh views — caching is unnecessary since both `SubArray` and +`ReshapedArray` are small structs that SROA can stack-allocate. + +Dispatches on `TypedPool{T}` (not `AbstractTypedPool`) because `_claim_slot!` +is only defined for `TypedPool{T}`. Other subtypes override `get_view!` directly +(e.g., `CuTypedPool`) or use a separate path (e.g., `BitTypedPool` → `get_bitarray!`). +""" +@inline function get_view!(tp::TypedPool{T}, n::Int) where {T} + idx = _claim_slot!(tp, n) + return @inbounds view(tp.vectors[idx], 1:n) +end + +@inline function get_view!(tp::TypedPool{T}, dims::NTuple{N, Int}) where {T, N} + total_len = safe_prod(dims) + slot = _claim_slot!(tp, total_len) + return @inbounds reshape(view(tp.vectors[slot], 1:total_len), dims) +end + # ============================================================================== # reshape! — Zero-Allocation Reshape (setfield!-based, Julia 1.11+) # ============================================================================== @@ -156,7 +153,7 @@ Zero-allocation reshape using `setfield!`-based wrapper reuse (Julia 1.11+). ) ) - # 0-D reshape: rare edge case, delegate to Base (nd_wrappers is 1-indexed by N) + # 0-D reshape: rare edge case, delegate to Base (arr_wrappers is 1-indexed by N) N == 0 && return reshape(A, dims) # Same dimensionality: just update size in-place, no pool interaction @@ -170,7 +167,7 @@ Zero-allocation reshape using `setfield!`-based wrapper reuse (Julia 1.11+). slot = _claim_slot!(tp) # Look up cached wrapper (direct index, no hash) - wrappers = N <= length(tp.nd_wrappers) ? (@inbounds tp.nd_wrappers[N]) : nothing + wrappers = N <= length(tp.arr_wrappers) ? (@inbounds tp.arr_wrappers[N]) : nothing if wrappers !== nothing && slot <= length(wrappers) wrapper = @inbounds wrappers[slot] if wrapper !== nothing @@ -185,7 +182,7 @@ Zero-allocation reshape using `setfield!`-based wrapper reuse (Julia 1.11+). arr = Array{T, N}(undef, ntuple(_ -> 0, Val(N))) setfield!(arr, :ref, getfield(A, :ref)) setfield!(arr, :size, dims) - _store_nd_wrapper!(tp, N, slot, arr) + _store_arr_wrapper!(tp, N, slot, arr) return arr end @@ -198,23 +195,23 @@ end # unlimited dimension patterns per slot, 0-alloc after warmup for any dims with same N. """ - _store_nd_wrapper!(tp::AbstractTypedPool, N::Int, slot::Int, wrapper) + _store_arr_wrapper!(tp::AbstractTypedPool, N::Int, slot::Int, wrapper) Store a cached N-D wrapper for the given slot. Creates the per-N Vector if needed. """ -function _store_nd_wrapper!(tp::AbstractTypedPool, N::Int, slot::Int, wrapper) - # Grow nd_wrappers vector so index N is valid - if N > length(tp.nd_wrappers) - old_len = length(tp.nd_wrappers) - resize!(tp.nd_wrappers, N) +function _store_arr_wrapper!(tp::AbstractTypedPool, N::Int, slot::Int, wrapper) + # Grow arr_wrappers vector so index N is valid + if N > length(tp.arr_wrappers) + old_len = length(tp.arr_wrappers) + resize!(tp.arr_wrappers, N) for i in (old_len + 1):N - @inbounds tp.nd_wrappers[i] = nothing + @inbounds tp.arr_wrappers[i] = nothing end end - wrappers = @inbounds tp.nd_wrappers[N] + wrappers = @inbounds tp.arr_wrappers[N] if wrappers === nothing wrappers = Vector{Any}(nothing, slot) - @inbounds tp.nd_wrappers[N] = wrappers + @inbounds tp.arr_wrappers[N] = wrappers elseif slot > length(wrappers) old_len = length(wrappers) resize!(wrappers, slot) @@ -227,26 +224,21 @@ function _store_nd_wrapper!(tp::AbstractTypedPool, N::Int, slot::Int, wrapper) end """ - get_nd_array!(tp::AbstractTypedPool{T,Vector{T}}, dims::NTuple{N,Int}) -> Array{T,N} + get_array!(tp::AbstractTypedPool{T,Vector{T}}, dims::NTuple{N,Int}) -> Array{T,N} Get an N-dimensional `Array` from the pool with `setfield!`-based wrapper reuse. -Uses Julia 1.11+ `setfield!` to mutate cached `Array` wrappers in-place: -- Same N (dimensionality): `setfield!(arr, :size, dims)` — 0 allocation -- Backing memory: `setfield!(arr, :ref, ...)` — always updated, 0 allocation in compiled code -- First call per (slot, N): `unsafe_wrap` once, then cached forever - -Unlike the N-way cache (Julia 1.10), this has no eviction limit — unlimited dimension -patterns per slot are supported with zero allocation after warmup. +Uses `_claim_slot!` directly for slot management (independent of view path). +Cache hit: `setfield!(arr, :ref/size)` — 0 allocation. +Cache miss: creates wrapper via `setfield!` pattern, then cached forever. """ -@inline function get_nd_array!(tp::AbstractTypedPool{T, Vector{T}}, dims::NTuple{N, Int}) where {T, N} +@inline function get_array!(tp::AbstractTypedPool{T, Vector{T}}, dims::NTuple{N, Int}) where {T, N} total_len = safe_prod(dims) - flat_view = get_view!(tp, total_len) # Increments n_active, ensures backing vec - slot = tp.n_active + slot = _claim_slot!(tp, total_len) @inbounds vec = tp.vectors[slot] # Look up cached wrapper for this dimensionality (direct index, no hash) - wrappers = N <= length(tp.nd_wrappers) ? (@inbounds tp.nd_wrappers[N]) : nothing + wrappers = N <= length(tp.arr_wrappers) ? (@inbounds tp.arr_wrappers[N]) : nothing if wrappers !== nothing && slot <= length(wrappers) wrapper = @inbounds wrappers[slot] if wrapper !== nothing @@ -261,23 +253,14 @@ patterns per slot are supported with zero allocation after warmup. end end - # Cache miss: first call for this (slot, N) — unsafe_wrap once - arr = wrap_array(tp, flat_view, dims) - _store_nd_wrapper!(tp, N, slot, arr) + # Cache miss: first call for this (slot, N) — create via setfield! pattern + arr = Array{T, N}(undef, ntuple(_ -> 0, Val(N))) + setfield!(arr, :ref, getfield(vec, :ref)) + setfield!(arr, :size, dims) + _store_arr_wrapper!(tp, N, slot, arr) return arr end -""" - get_nd_view!(tp::AbstractTypedPool{T}, dims::NTuple{N,Int}) - -Get an N-dimensional view via `reshape` (zero creation cost). -""" -@inline function get_nd_view!(tp::AbstractTypedPool{T}, dims::NTuple{N, Int}) where {T, N} - total_len = safe_prod(dims) - flat_view = get_view!(tp, total_len) # 1D view (cached, 0 alloc) - return reshape(flat_view, dims) # ReshapedArray (0 creation cost) -end - # ============================================================================== # Type Touch Recording (for selective rewind) # ============================================================================== @@ -349,7 +332,7 @@ end @inline function _acquire_impl!(pool::AbstractArrayPool, ::Type{T}, dims::Vararg{Int, N}) where {T, N} tp = get_typed_pool!(pool, T) - return get_nd_view!(tp, dims) + return get_view!(tp, dims) end @inline function _acquire_impl!(pool::AbstractArrayPool, ::Type{T}, dims::NTuple{N, Int}) where {T, N} @@ -366,17 +349,17 @@ Internal implementation of unsafe_acquire!. Called directly by macro-transformed """ @inline function _unsafe_acquire_impl!(pool::AbstractArrayPool, ::Type{T}, n::Int) where {T} tp = get_typed_pool!(pool, T) - return get_nd_array!(tp, (n,)) + return get_array!(tp, (n,)) end @inline function _unsafe_acquire_impl!(pool::AbstractArrayPool, ::Type{T}, dims::Vararg{Int, N}) where {T, N} tp = get_typed_pool!(pool, T) - return get_nd_array!(tp, dims) + return get_array!(tp, dims) end @inline function _unsafe_acquire_impl!(pool::AbstractArrayPool, ::Type{T}, dims::NTuple{N, Int}) where {T, N} tp = get_typed_pool!(pool, T) - return get_nd_array!(tp, dims) + return get_array!(tp, dims) end # Similar-style diff --git a/src/bitarray.jl b/src/bitarray.jl index 8f789b5..8ec7c92 100644 --- a/src/bitarray.jl +++ b/src/bitarray.jl @@ -11,6 +11,9 @@ # - _acquire_impl! for Bit - Delegates to _unsafe_acquire_impl! for performance # - _unsafe_acquire_impl! for Bit - Raw BitArray acquisition with caching # - DisabledPool fallbacks for Bit type +# - empty!(::BitTypedPool) - State management (clearing pool storage) +# - _check_bitchunks_overlap - Safety validation for POOL_DEBUG mode +# - Display helpers: _default_type_name, _vector_bytes, _count_label, _show_type_name # # Design Decision: Unified BitArray Return Type # ============================================= @@ -62,7 +65,7 @@ function get_bitarray!(tp::BitTypedPool, dims::NTuple{N, Int}) where {N} ba.chunks = pool_bv.chunks # Cache the wrapper - _store_nd_wrapper!(tp, N, idx, ba) + _store_arr_wrapper!(tp, N, idx, ba) # Warn at powers of 2 (possible missing rewind!) if idx >= 512 && (idx & (idx - 1)) == 0 @@ -80,7 +83,7 @@ function get_bitarray!(tp::BitTypedPool, dims::NTuple{N, Int}) where {N} end # 3. Check wrapper cache (direct index, no hash) - wrappers = N <= length(tp.nd_wrappers) ? (@inbounds tp.nd_wrappers[N]) : nothing + wrappers = N <= length(tp.arr_wrappers) ? (@inbounds tp.arr_wrappers[N]) : nothing if wrappers !== nothing && idx <= length(wrappers) wrapper = @inbounds wrappers[idx] if wrapper !== nothing @@ -96,7 +99,7 @@ function get_bitarray!(tp::BitTypedPool, dims::NTuple{N, Int}) where {N} # 4. Cache miss: first call for this (slot, N) ba = BitArray{N}(undef, dims) ba.chunks = pool_bv.chunks - _store_nd_wrapper!(tp, N, idx, ba) + _store_arr_wrapper!(tp, N, idx, ba) return ba end @@ -162,3 +165,57 @@ end @inline unsafe_acquire!(::DisabledPool{:cpu}, ::Type{Bit}, n::Int) = BitVector(undef, n) @inline unsafe_acquire!(::DisabledPool{:cpu}, ::Type{Bit}, dims::Vararg{Int, N}) where {N} = BitArray{N}(undef, dims) @inline unsafe_acquire!(::DisabledPool{:cpu}, ::Type{Bit}, dims::NTuple{N, Int}) where {N} = BitArray{N}(undef, dims) + +# ============================================================================== +# State Management — empty! +# ============================================================================== + +""" + empty!(tp::BitTypedPool) + +Clear all internal storage for BitTypedPool, releasing all memory. +Restores sentinel values for 1-based sentinel pattern. +""" +function Base.empty!(tp::BitTypedPool) + empty!(tp.vectors) + empty!(tp.arr_wrappers) + tp.n_active = 0 + # Restore sentinel values (1-based sentinel pattern) + empty!(tp._checkpoint_n_active) + push!(tp._checkpoint_n_active, 0) # Sentinel: n_active=0 at depth=0 + empty!(tp._checkpoint_depths) + push!(tp._checkpoint_depths, 0) # Sentinel: depth=0 = no checkpoint + return tp +end + +# ============================================================================== +# Safety Validation (POOL_DEBUG mode) +# ============================================================================== + +# Check if BitArray chunks overlap with the pool's BitTypedPool storage +function _check_bitchunks_overlap(arr::BitArray, pool::AdaptiveArrayPool) + arr_chunks = arr.chunks + arr_ptr = UInt(pointer(arr_chunks)) + arr_len = length(arr_chunks) * sizeof(UInt64) + arr_end = arr_ptr + arr_len + + for v in pool.bits.vectors + v_chunks = v.chunks + v_ptr = UInt(pointer(v_chunks)) + v_len = length(v_chunks) * sizeof(UInt64) + v_end = v_ptr + v_len + if !(arr_end <= v_ptr || v_end <= arr_ptr) + error("Safety Violation: The function returned a BitArray backed by pool memory. This is unsafe as the memory will be reclaimed. Please return a copy (copy) or a scalar.") + end + end + return nothing +end + +# ============================================================================== +# Display Helpers (pool_stats / Base.show) +# ============================================================================== + +_default_type_name(::BitTypedPool) = "Bit" +_vector_bytes(v::BitVector) = sizeof(v.chunks) +_count_label(::BitTypedPool) = "bits" +_show_type_name(::BitTypedPool) = "BitTypedPool" diff --git a/src/legacy/acquire.jl b/src/legacy/acquire.jl index 1d94dfe..c1ab78d 100644 --- a/src/legacy/acquire.jl +++ b/src/legacy/acquire.jl @@ -96,7 +96,7 @@ end # (dims, pointer) patterns per slot via round-robin replacement. """ - get_nd_array!(tp::AbstractTypedPool{T}, dims::NTuple{N,Int}) -> Array{T,N} + get_array!(tp::AbstractTypedPool{T}, dims::NTuple{N,Int}) -> Array{T,N} Get an N-dimensional `Array` from the pool with N-way caching. @@ -105,7 +105,7 @@ Cache hit (exact dims + pointer match) returns the cached Array at zero cost. Cache miss creates a new `unsafe_wrap`'d Array (~96 bytes) and stores it via round-robin replacement. """ -@inline function get_nd_array!(tp::AbstractTypedPool{T}, dims::NTuple{N, Int}) where {T, N} +@inline function get_array!(tp::AbstractTypedPool{T}, dims::NTuple{N, Int}) where {T, N} total_len = safe_prod(dims) flat_view = get_view!(tp, total_len) # Increments n_active slot = tp.n_active @@ -253,17 +253,17 @@ Internal implementation of unsafe_acquire!. Called directly by macro-transformed """ @inline function _unsafe_acquire_impl!(pool::AbstractArrayPool, ::Type{T}, n::Int) where {T} tp = get_typed_pool!(pool, T) - return get_nd_array!(tp, (n,)) + return get_array!(tp, (n,)) end @inline function _unsafe_acquire_impl!(pool::AbstractArrayPool, ::Type{T}, dims::Vararg{Int, N}) where {T, N} tp = get_typed_pool!(pool, T) - return get_nd_array!(tp, dims) + return get_array!(tp, dims) end @inline function _unsafe_acquire_impl!(pool::AbstractArrayPool, ::Type{T}, dims::NTuple{N, Int}) where {T, N} tp = get_typed_pool!(pool, T) - return get_nd_array!(tp, dims) + return get_array!(tp, dims) end # Similar-style diff --git a/src/legacy/bitarray.jl b/src/legacy/bitarray.jl index 7964e8c..a2bdbfa 100644 --- a/src/legacy/bitarray.jl +++ b/src/legacy/bitarray.jl @@ -11,6 +11,9 @@ # - _acquire_impl! for Bit - Delegates to _unsafe_acquire_impl! for performance # - _unsafe_acquire_impl! for Bit - Raw BitArray acquisition with caching # - DisabledPool fallbacks for Bit type +# - empty!(::BitTypedPool) - State management (clearing pool storage) +# - _check_bitchunks_overlap - Safety validation for POOL_DEBUG mode +# - Display helpers: _default_type_name, _vector_bytes, _count_label, _show_type_name # # Design Decision: Unified BitArray Return Type # ============================================= @@ -203,3 +206,61 @@ end @inline unsafe_acquire!(::DisabledPool{:cpu}, ::Type{Bit}, n::Int) = BitVector(undef, n) @inline unsafe_acquire!(::DisabledPool{:cpu}, ::Type{Bit}, dims::Vararg{Int, N}) where {N} = BitArray{N}(undef, dims) @inline unsafe_acquire!(::DisabledPool{:cpu}, ::Type{Bit}, dims::NTuple{N, Int}) where {N} = BitArray{N}(undef, dims) + +# ============================================================================== +# State Management — empty! (Legacy: N-way cache fields) +# ============================================================================== + +""" + empty!(tp::BitTypedPool) + +Clear all internal storage for BitTypedPool, releasing all memory. +Restores sentinel values for 1-based sentinel pattern. +""" +function Base.empty!(tp::BitTypedPool) + empty!(tp.vectors) + # Clear N-way cache + empty!(tp.nd_arrays) + empty!(tp.nd_dims) + empty!(tp.nd_ptrs) + empty!(tp.nd_next_way) + tp.n_active = 0 + # Restore sentinel values (1-based sentinel pattern) + empty!(tp._checkpoint_n_active) + push!(tp._checkpoint_n_active, 0) # Sentinel: n_active=0 at depth=0 + empty!(tp._checkpoint_depths) + push!(tp._checkpoint_depths, 0) # Sentinel: depth=0 = no checkpoint + return tp +end + +# ============================================================================== +# Safety Validation (POOL_DEBUG mode) +# ============================================================================== + +# Check if BitArray chunks overlap with the pool's BitTypedPool storage +function _check_bitchunks_overlap(arr::BitArray, pool::AdaptiveArrayPool) + arr_chunks = arr.chunks + arr_ptr = UInt(pointer(arr_chunks)) + arr_len = length(arr_chunks) * sizeof(UInt64) + arr_end = arr_ptr + arr_len + + for v in pool.bits.vectors + v_chunks = v.chunks + v_ptr = UInt(pointer(v_chunks)) + v_len = length(v_chunks) * sizeof(UInt64) + v_end = v_ptr + v_len + if !(arr_end <= v_ptr || v_end <= arr_ptr) + error("Safety Violation: The function returned a BitArray backed by pool memory. This is unsafe as the memory will be reclaimed. Please return a copy (copy) or a scalar.") + end + end + return nothing +end + +# ============================================================================== +# Display Helpers (pool_stats / Base.show) +# ============================================================================== + +_default_type_name(::BitTypedPool) = "Bit" +_vector_bytes(v::BitVector) = sizeof(v.chunks) +_count_label(::BitTypedPool) = "bits" +_show_type_name(::BitTypedPool) = "BitTypedPool" diff --git a/src/legacy/state.jl b/src/legacy/state.jl index 71366b1..71848e2 100644 --- a/src/legacy/state.jl +++ b/src/legacy/state.jl @@ -383,28 +383,6 @@ end # State Management - empty! (Legacy: N-way cache fields) # ============================================================================== -""" - empty!(tp::BitTypedPool) - -Clear all internal storage for BitTypedPool, releasing all memory. -Restores sentinel values for 1-based sentinel pattern. -""" -function Base.empty!(tp::BitTypedPool) - empty!(tp.vectors) - # Clear N-way cache - empty!(tp.nd_arrays) - empty!(tp.nd_dims) - empty!(tp.nd_ptrs) - empty!(tp.nd_next_way) - tp.n_active = 0 - # Restore sentinel values (1-based sentinel pattern) - empty!(tp._checkpoint_n_active) - push!(tp._checkpoint_n_active, 0) # Sentinel: n_active=0 at depth=0 - empty!(tp._checkpoint_depths) - push!(tp._checkpoint_depths, 0) # Sentinel: depth=0 = no checkpoint - return tp -end - """ empty!(tp::TypedPool) diff --git a/src/state.jl b/src/state.jl index ec172b4..f777ad4 100644 --- a/src/state.jl +++ b/src/state.jl @@ -380,24 +380,6 @@ end # State Management - empty! # ============================================================================== -""" - empty!(tp::BitTypedPool) - -Clear all internal storage for BitTypedPool, releasing all memory. -Restores sentinel values for 1-based sentinel pattern. -""" -function Base.empty!(tp::BitTypedPool) - empty!(tp.vectors) - empty!(tp.nd_wrappers) - tp.n_active = 0 - # Restore sentinel values (1-based sentinel pattern) - empty!(tp._checkpoint_n_active) - push!(tp._checkpoint_n_active, 0) # Sentinel: n_active=0 at depth=0 - empty!(tp._checkpoint_depths) - push!(tp._checkpoint_depths, 0) # Sentinel: depth=0 = no checkpoint - return tp -end - """ empty!(tp::TypedPool) @@ -406,9 +388,7 @@ Restores sentinel values for 1-based sentinel pattern. """ function Base.empty!(tp::TypedPool) empty!(tp.vectors) - empty!(tp.views) - empty!(tp.view_lengths) - empty!(tp.nd_wrappers) + empty!(tp.arr_wrappers) tp.n_active = 0 # Restore sentinel values (1-based sentinel pattern) empty!(tp._checkpoint_n_active) @@ -489,7 +469,7 @@ This function: - Restores all checkpoint stacks to sentinel state - Resets `_current_depth` and type touch tracking state -Unlike `empty!`, this **preserves** all allocated vectors, views, and N-D arrays +Unlike `empty!`, this **preserves** all allocated vectors and N-D wrapper caches for reuse, avoiding reallocation costs. ## Use Case diff --git a/src/types.jl b/src/types.jl index 130479b..139d3c1 100644 --- a/src/types.jl +++ b/src/types.jl @@ -1,66 +1,3 @@ -# ============================================================================== -# Constants (Configurable via Preferences) -# ============================================================================== - -using Preferences - -""" -Number of cache ways per slot for N-way set associative cache. -Supports up to `CACHE_WAYS` different dimension patterns per slot without thrashing. - -Default: 4 (handles most use cases well) - -## Configuration -```julia -using AdaptiveArrayPools -AdaptiveArrayPools.set_cache_ways!(8) # Restart Julia to take effect -``` - -Or manually in `LocalPreferences.toml`: -```toml -[AdaptiveArrayPools] -cache_ways = 8 -``` - -Valid range: 1-16 (higher values increase memory but reduce eviction) -""" -const CACHE_WAYS = let - ways = @load_preference("cache_ways", 4)::Int - if ways < 1 || ways > 16 - @warn "CACHE_WAYS=$ways out of range [1,16], using default 4" - 4 - else - ways - end -end - -""" - set_cache_ways!(n::Int) - -Set the number of cache ways for N-D array caching. -**Requires Julia restart to take effect.** - -Higher values reduce cache eviction but increase memory usage per slot. - -## Arguments -- `n::Int`: Number of cache ways (valid range: 1-16) - -## Example -```julia -using AdaptiveArrayPools -AdaptiveArrayPools.set_cache_ways!(8) # Double the default -# Restart Julia to apply the change -``` -""" -function set_cache_ways!(n::Int) - if n < 1 || n > 16 - throw(ArgumentError("cache_ways must be in range [1, 16], got $n")) - end - @set_preferences!("cache_ways" => n) - @info "CACHE_WAYS set to $n. Restart Julia to apply." - return n -end - # ============================================================================== # Abstract Type Hierarchy (for extensibility) # ============================================================================== @@ -154,12 +91,8 @@ Internal structure managing pooled vectors for a specific element type `T`. ### Storage - `vectors`: Backing `Vector{T}` storage (actual memory allocation) -### 1D Cache (for `acquire!(pool, T, n)`) -- `views`: Cached `SubArray` views for zero-allocation 1D access -- `view_lengths`: Cached lengths for fast Int comparison (SoA pattern) - ### N-D Wrapper Cache (Julia 1.11+, setfield!-based reuse) -- `nd_wrappers`: `Vector{Union{Nothing, Vector{Any}}}` — indexed by N (dimensionality), +- `arr_wrappers`: `Vector{Union{Nothing, Vector{Any}}}` — indexed by N (dimensionality), each entry is a per-slot cached `Array{T,N}` wrapper. Uses `setfield!(wrapper, :size, dims)` and `setfield!(wrapper, :ref, parent)` for zero-allocation reuse of unlimited dim patterns. @@ -168,20 +101,18 @@ Internal structure managing pooled vectors for a specific element type `T`. - `_checkpoint_n_active`: Saved n_active values at each checkpoint (sentinel: `[0]`) - `_checkpoint_depths`: Depth of each checkpoint entry (sentinel: `[0]`) -## Note -`acquire!` for N-D returns `ReshapedArray` (zero creation cost), so no caching needed. -`unsafe_acquire!` uses `setfield!` wrapper reuse — unlimited dim patterns, 0-alloc after warmup. +## Design Notes +- 1D views (`SubArray`) are created fresh on every `acquire!` call — SubArray is stack-allocated + via SROA in modern Julia, making caching unnecessary (and slower due to memory indirection). +- `unsafe_acquire!` uses `setfield!` wrapper reuse — unlimited dim patterns, 0-alloc after warmup. +- Slot management is unified via `_claim_slot!` — the shared primitive for all acquisition paths. """ mutable struct TypedPool{T} <: AbstractTypedPool{T, Vector{T}} # --- Storage --- vectors::Vector{Vector{T}} - # --- 1D Cache (1:1 mapping) --- - views::Vector{SubArray{T, 1, Vector{T}, Tuple{UnitRange{Int64}}, true}} - view_lengths::Vector{Int} - # --- N-D Wrapper Cache (setfield!-based reuse) --- - nd_wrappers::Vector{Union{Nothing, Vector{Any}}} # index=N (dimensionality), value=per-slot Array{T,N} + arr_wrappers::Vector{Union{Nothing, Vector{Any}}} # index=N (dimensionality), value=per-slot Array{T,N} # --- State Management (1-based sentinel pattern) --- n_active::Int @@ -192,9 +123,6 @@ end TypedPool{T}() where {T} = TypedPool{T}( # Storage Vector{T}[], - # 1D Cache - SubArray{T, 1, Vector{T}, Tuple{UnitRange{Int64}}, true}[], - Int[], # N-D Wrapper Cache Union{Nothing, Vector{Any}}[], # State Management (1-based sentinel pattern: guaranteed non-empty) @@ -284,7 +212,7 @@ performance without needing to choose between APIs. ## Fields - `vectors`: Backing `BitVector` storage -- `nd_wrappers`: `Vector{Union{Nothing, Vector{Any}}}` — setfield!-based cache (Julia 1.11+) +- `arr_wrappers`: `Vector{Union{Nothing, Vector{Any}}}` — setfield!-based cache (Julia 1.11+) - `n_active`: Count of currently active arrays - `_checkpoint_*`: State management stacks (1-based sentinel pattern) @@ -300,7 +228,7 @@ mutable struct BitTypedPool <: AbstractTypedPool{Bool, BitVector} vectors::Vector{BitVector} # --- N-D Wrapper Cache (setfield!-based reuse) --- - nd_wrappers::Vector{Union{Nothing, Vector{Any}}} # index=N (dimensionality), value=per-slot BitArray{N} + arr_wrappers::Vector{Union{Nothing, Vector{Any}}} # index=N (dimensionality), value=per-slot BitArray{N} # --- State Management (1-based sentinel pattern) --- n_active::Int diff --git a/src/utils.jl b/src/utils.jl index b528c2d..4b77d21 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -14,6 +14,7 @@ const POOL_DEBUG = Ref(false) function _validate_pool_return(val, pool::AdaptiveArrayPool) # 0. Check BitArray / BitVector (bit-packed storage) + # Note: _check_bitchunks_overlap is defined in bitarray.jl / legacy/bitarray.jl (included after utils.jl) if val isa BitArray _check_bitchunks_overlap(val, pool) return @@ -85,25 +86,6 @@ function _check_pointer_overlap(arr::Array, pool::AdaptiveArrayPool) return end -# Check if BitArray chunks overlap with the pool's BitTypedPool storage -function _check_bitchunks_overlap(arr::BitArray, pool::AdaptiveArrayPool) - arr_chunks = arr.chunks - arr_ptr = UInt(pointer(arr_chunks)) - arr_len = length(arr_chunks) * sizeof(UInt64) - arr_end = arr_ptr + arr_len - - for v in pool.bits.vectors - v_chunks = v.chunks - v_ptr = UInt(pointer(v_chunks)) - v_len = length(v_chunks) * sizeof(UInt64) - v_end = v_ptr + v_len - if !(arr_end <= v_ptr || v_end <= arr_ptr) - error("Safety Violation: The function returned a BitArray backed by pool memory. This is unsafe as the memory will be reclaimed. Please return a copy (copy) or a scalar.") - end - end - return nothing -end - _validate_pool_return(val, ::DisabledPool) = nothing # ============================================================================== @@ -112,13 +94,10 @@ _validate_pool_return(val, ::DisabledPool) = nothing # --- Helper functions for pool_stats (type-specific behavior) --- _default_type_name(::TypedPool{T}) where {T} = string(T) -_default_type_name(::BitTypedPool) = "Bit" _vector_bytes(v::Vector) = Base.summarysize(v) -_vector_bytes(v::BitVector) = sizeof(v.chunks) _count_label(::TypedPool) = "elements" -_count_label(::BitTypedPool) = "bits" """ pool_stats(tp::AbstractTypedPool; io::IO=stdout, indent::Int=0, name::String="") @@ -252,7 +231,6 @@ end # --- Helper for Base.show (full type name for display) --- _show_type_name(::TypedPool{T}) where {T} = "TypedPool{$T}" -_show_type_name(::BitTypedPool) = "BitTypedPool" # Compact one-line show for all AbstractTypedPool function Base.show(io::IO, tp::AbstractTypedPool) diff --git a/test/cuda/test_extension.jl b/test/cuda/test_extension.jl index c4cc379..01753a3 100644 --- a/test/cuda/test_extension.jl +++ b/test/cuda/test_extension.jl @@ -52,15 +52,6 @@ end @test length(vec) == 100 end - @testset "wrap_array" begin - tp = CuTypedPool{Float32}() - vec = CUDA.zeros(Float32, 50) - flat_view = view(vec, 1:50) - wrapped = AdaptiveArrayPools.wrap_array(tp, flat_view, (10, 5)) - @test wrapped isa CuArray{Float32, 2} - @test size(wrapped) == (10, 5) - end - @testset "get_typed_pool! fixed slots" begin pool = CuAdaptiveArrayPool() test_types = [Float32, Float64, Float16, Int32, Int64, ComplexF32, ComplexF64, Bool] diff --git a/test/cuda/test_nway_cache.jl b/test/cuda/test_nway_cache.jl index af86266..526c5e5 100644 --- a/test/cuda/test_nway_cache.jl +++ b/test/cuda/test_nway_cache.jl @@ -29,9 +29,10 @@ end @testset "CACHE_WAYS configuration" begin - # Verify CACHE_WAYS is accessible - @test AdaptiveArrayPools.CACHE_WAYS isa Int - @test 1 <= AdaptiveArrayPools.CACHE_WAYS <= 16 + # CACHE_WAYS is defined in CUDA extension (CPU ≥1.11 no longer uses view caching) + CUDAExt = Base.get_extension(AdaptiveArrayPools, :AdaptiveArrayPoolsCUDAExt) + @test CUDAExt.CACHE_WAYS isa Int + @test 1 <= CUDAExt.CACHE_WAYS <= 16 end end diff --git a/test/runtests.jl b/test/runtests.jl index f017cf2..ba9dbb0 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -5,7 +5,7 @@ import AdaptiveArrayPools: checkpoint!, rewind! # Version-specific helpers (always defined, even for ARGS path) @static if VERSION >= v"1.11-" - _test_nd_cache_preserved(tp) = !isempty(tp.nd_wrappers) + _test_nd_cache_preserved(tp) = !isempty(tp.arr_wrappers) else _test_nd_cache_preserved(tp) = length(tp.nd_arrays) >= 1 end diff --git a/test/test_basic.jl b/test/test_basic.jl index 3a42077..de1b5c7 100644 --- a/test/test_basic.jl +++ b/test/test_basic.jl @@ -53,6 +53,44 @@ @test length(v2) == 5 end + @testset "Slot reuse with resize via _claim_slot!" begin + pool = AdaptiveArrayPool() + + # Acquire slot with small size, write sentinel data to slot 2 + checkpoint!(pool) + v1 = acquire!(pool, Float64, 10) + v1 .= 1.0 + v2 = acquire!(pool, Float64, 20) + v2 .= 2.0 + @test pool.float64.n_active == 2 + @test length(pool.float64.vectors[1]) >= 10 + rewind!(pool) + + # Re-acquire slot 1 with LARGER size — triggers resize in _claim_slot! + checkpoint!(pool) + v1_big = acquire!(pool, Float64, 200) + @test length(v1_big) == 200 + @test length(pool.float64.vectors[1]) >= 200 # backing vec grew + + # Slot 2 reused at original size — should not be affected by slot 1 resize + v2_reuse = acquire!(pool, Float64, 15) + v2_reuse .= 3.0 + @test length(v2_reuse) == 15 + @test length(pool.float64.vectors[2]) >= 15 # unchanged or grown, not corrupted + + # Verify independence: writing to v1_big doesn't corrupt v2_reuse + v1_big .= 99.0 + @test all(v2_reuse .== 3.0) + rewind!(pool) + + # Re-acquire slot 1 with SMALLER size — no resize needed, backing vec stays large + checkpoint!(pool) + v1_small = acquire!(pool, Float64, 5) + @test length(v1_small) == 5 + @test length(pool.float64.vectors[1]) >= 200 # capacity preserved from earlier grow + rewind!(pool) + end + @testset "Fixed slot types" begin pool = AdaptiveArrayPool() diff --git a/test/test_coverage.jl b/test/test_coverage.jl index 047bad5..ee6ae44 100644 --- a/test/test_coverage.jl +++ b/test/test_coverage.jl @@ -343,11 +343,7 @@ @test_throws MethodError pool_stats(:cuda) end - @testset "set_cache_ways! validation" begin - # Test invalid range - @test_throws ArgumentError AdaptiveArrayPools.set_cache_ways!(0) - @test_throws ArgumentError AdaptiveArrayPools.set_cache_ways!(17) - end + # set_cache_ways! tests are in test/legacy/test_nway_cache.jl (legacy only) @testset "_transform_acquire_calls with qualified names" begin # Test qualified name transformation (AdaptiveArrayPools.function!) diff --git a/test/test_nway_cache.jl b/test/test_nway_cache.jl index 33cb64e..82aeb46 100644 --- a/test/test_nway_cache.jl +++ b/test/test_nway_cache.jl @@ -22,31 +22,8 @@ using AdaptiveArrayPools end end - @testset "CACHE_WAYS configuration" begin - # Verify CACHE_WAYS is exported and accessible - @test CACHE_WAYS isa Int - @test 1 <= CACHE_WAYS <= 16 # Valid range - - # Verify set_cache_ways! is exported - @test isdefined(AdaptiveArrayPools, :set_cache_ways!) - end - - @testset "set_cache_ways! validation" begin - # Valid values should return the input value - @test set_cache_ways!(1) == 1 - @test set_cache_ways!(4) == 4 - @test set_cache_ways!(8) == 8 - @test set_cache_ways!(16) == 16 - - # Invalid values should throw ArgumentError - @test_throws ArgumentError set_cache_ways!(0) - @test_throws ArgumentError set_cache_ways!(-1) - @test_throws ArgumentError set_cache_ways!(17) - @test_throws ArgumentError set_cache_ways!(100) - - # Reset to default after tests - set_cache_ways!(4) - end + # CACHE_WAYS and set_cache_ways! are legacy-only (Julia ≤1.10) + # See test/legacy/test_nway_cache.jl for those tests. end @@ -219,28 +196,28 @@ end # ============================================================================== # Vector-Based N-D Wrapper Cache Tests (Julia 1.11+) # ============================================================================== -# These tests verify the Dict→Vector migration for nd_wrappers. +# These tests verify the Dict→Vector migration for arr_wrappers. -@testset "Vector-based nd_wrappers cache" begin +@testset "Vector-based arr_wrappers cache" begin using AdaptiveArrayPools: checkpoint!, rewind! - @testset "nd_wrappers grows correctly for multiple dimensionalities" begin + @testset "arr_wrappers grows correctly for multiple dimensionalities" begin pool = AdaptiveArrayPool() checkpoint!(pool) # N=1: 1D unsafe_acquire v1 = unsafe_acquire!(pool, Float64, 10) - @test length(pool.float64.nd_wrappers) >= 1 + @test length(pool.float64.arr_wrappers) >= 1 - # N=2: 2D unsafe_acquire — nd_wrappers should grow to index 2 + # N=2: 2D unsafe_acquire — arr_wrappers should grow to index 2 m1 = unsafe_acquire!(pool, Float64, 3, 4) - @test length(pool.float64.nd_wrappers) >= 2 - @test pool.float64.nd_wrappers[2] !== nothing # has a Vector{Any} for N=2 + @test length(pool.float64.arr_wrappers) >= 2 + @test pool.float64.arr_wrappers[2] !== nothing # has a Vector{Any} for N=2 - # N=3: 3D unsafe_acquire — nd_wrappers should grow to index 3 + # N=3: 3D unsafe_acquire — arr_wrappers should grow to index 3 t1 = unsafe_acquire!(pool, Float64, 2, 3, 4) - @test length(pool.float64.nd_wrappers) >= 3 - @test pool.float64.nd_wrappers[3] !== nothing # has a Vector{Any} for N=3 + @test length(pool.float64.arr_wrappers) >= 3 + @test pool.float64.arr_wrappers[3] !== nothing # has a Vector{Any} for N=3 rewind!(pool) end @@ -283,37 +260,37 @@ end @test size(t) == (2, 3, 4) # Both N=2 and N=3 entries exist - @test pool.float64.nd_wrappers[2] !== nothing - @test pool.float64.nd_wrappers[3] !== nothing + @test pool.float64.arr_wrappers[2] !== nothing + @test pool.float64.arr_wrappers[3] !== nothing rewind!(pool) end - @testset "nd_wrappers with nothing gaps for skipped N" begin + @testset "arr_wrappers with nothing gaps for skipped N" begin pool = AdaptiveArrayPool() checkpoint!(pool) # Jump directly to N=3 without using N=2 t = unsafe_acquire!(pool, Float64, 2, 3, 4) - @test length(pool.float64.nd_wrappers) >= 3 + @test length(pool.float64.arr_wrappers) >= 3 # N=2 entry should be nothing (never used for N=2) - @test pool.float64.nd_wrappers[2] === nothing + @test pool.float64.arr_wrappers[2] === nothing rewind!(pool) end - @testset "BitTypedPool nd_wrappers cache" begin + @testset "BitTypedPool arr_wrappers cache" begin pool = AdaptiveArrayPool() checkpoint!(pool) # 1D BitArray bv = acquire!(pool, Bit, 100) - @test length(pool.bits.nd_wrappers) >= 1 + @test length(pool.bits.arr_wrappers) >= 1 # 2D BitArray ba = acquire!(pool, Bit, 10, 10) - @test length(pool.bits.nd_wrappers) >= 2 - @test pool.bits.nd_wrappers[2] !== nothing + @test length(pool.bits.arr_wrappers) >= 2 + @test pool.bits.arr_wrappers[2] !== nothing rewind!(pool) @@ -325,29 +302,29 @@ end rewind!(pool) end - @testset "empty! clears nd_wrappers" begin + @testset "empty! clears arr_wrappers" begin pool = AdaptiveArrayPool() checkpoint!(pool) unsafe_acquire!(pool, Float64, 3, 4) rewind!(pool) - @test !isempty(pool.float64.nd_wrappers) + @test !isempty(pool.float64.arr_wrappers) empty!(pool) - @test isempty(pool.float64.nd_wrappers) + @test isempty(pool.float64.arr_wrappers) end - @testset "multiple element types have independent nd_wrappers" begin + @testset "multiple element types have independent arr_wrappers" begin pool = AdaptiveArrayPool() checkpoint!(pool) mf = unsafe_acquire!(pool, Float64, 3, 4) mi = unsafe_acquire!(pool, Int64, 5, 6) - @test pool.float64.nd_wrappers[2] !== nothing - @test pool.int64.nd_wrappers[2] !== nothing + @test pool.float64.arr_wrappers[2] !== nothing + @test pool.int64.arr_wrappers[2] !== nothing # They must be separate Vector{Any} instances - @test pool.float64.nd_wrappers[2] !== pool.int64.nd_wrappers[2] + @test pool.float64.arr_wrappers[2] !== pool.int64.arr_wrappers[2] rewind!(pool) end diff --git a/test/test_state.jl b/test/test_state.jl index ce18668..9f528b9 100644 --- a/test/test_state.jl +++ b/test/test_state.jl @@ -224,8 +224,6 @@ import AdaptiveArrayPools: _typed_lazy_checkpoint!, _typed_lazy_rewind!, _tracke # Verify all fixed slots are cleared @test isempty(pool.float64.vectors) - @test isempty(pool.float64.views) - @test isempty(pool.float64.view_lengths) @test pool.float64.n_active == 0 @test length(pool.float64._checkpoint_n_active) == 1 # Only sentinel remains @@ -286,7 +284,6 @@ import AdaptiveArrayPools: _typed_lazy_checkpoint!, _typed_lazy_rewind!, _tracke reset!(pool) @test pool.float64.n_active == 0 @test length(pool.float64.vectors) >= 3 # Vectors preserved - @test length(pool.float64.views) >= 1 # 1D cache preserved @test _test_nd_cache_preserved(pool.float64) # N-D cache preserved end