Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/make.jl
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ makedocs(
],
"Features" => [
"`@maybe_with_pool`" => "features/maybe-with-pool.md",
"Bit Arrays" => "features/bit-arrays.md",
"CUDA Support" => "features/cuda-support.md",
"Configuration" => "features/configuration.md",
],
Expand Down
78 changes: 78 additions & 0 deletions docs/src/features/bit-arrays.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# BitVector Support

AdaptiveArrayPools.jl includes specialized support for `BitArray` (specifically `BitVector`), enabling **~8x memory savings** for boolean arrays compared to standard `Vector{Bool}`.

## The `Bit` Sentinel Type

To distinguish between standard boolean arrays (`Vector{Bool}`, 1 byte/element) and bit-packed arrays (`BitVector`, 1 bit/element), use the `Bit` sentinel type.

| Call | Result | Memory |
|------|--------|--------|
| `acquire!(pool, Bool, 1000)` | `Vector{Bool}` | 1000 bytes |
| `acquire!(pool, Bit, 1000)` | `BitVector` | ~125 bytes |

## Usage

### 1D Arrays (BitVector)
For 1D arrays, `acquire!` returns a view into a pooled `BitVector`.

```julia
@with_pool pool begin
# Acquire a BitVector of length 1000
bv = acquire!(pool, Bit, 1000)

# Use like normal
bv .= true
bv[1] = false

# Supports standard operations
count(bv)
end
```

### N-D Arrays (BitArray / Reshaped)
For multi-dimensional arrays, `acquire!` returns a `ReshapedArray` wrapper around the linear `BitVector`. This maintains zero-allocation efficiency while providing N-D indexing.

```julia
@with_pool pool begin
# 100x100 bit matrix
mask = zeros!(pool, Bit, 100, 100)

mask[5, 5] = true
end
```

### Convenience Functions

For specific `BitVector` operations, prefer `trues!` and `falses!` which mirror Julia's standard functions:

```julia
@with_pool pool begin
# Filled with false (equivalent to `falses(256)`)
mask = falses!(pool, 256)

# Filled with true (equivalent to `trues(256)`)
flags = trues!(pool, 256)

# Multidimensional
grid = trues!(pool, 100, 100)

# Similar to existing BitArray
A = BitVector(undef, 50)
B = similar!(pool, A) # Reuses eltype(A) -> Bool

# To explicit get Bit-packed from pool irrespective of source
C = similar!(pool, A, Bit)
end

Note: `zeros!(pool, Bit, ...)` and `ones!(pool, Bit, ...)` are also supported (aliased to `falses!` and `trues!`).
```

## How It Works

The pool maintains a separate `BitTypedPool` specifically for `BitVector` storage.
- **Sentinel**: `acquire!(..., Bit, ...)` dispatches to this special pool.
- **Views**: 1D returns `SubArray{Bool, 1, BitVector, ...}`.
- **Reshaping**: N-D returns `ReshapedArray{Bool, N, SubArray{...}}`.

This ensures that even for complex shapes, the underlying storage is always a compact `BitVector` reused from the pool.
3 changes: 2 additions & 1 deletion src/AdaptiveArrayPools.jl
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ using Printf
# Public API
export AdaptiveArrayPool, acquire!, unsafe_acquire!, pool_stats, get_task_local_pool
export acquire_view!, acquire_array! # Explicit naming aliases
export zeros!, ones!, similar!, default_eltype # Convenience functions
export zeros!, ones!, trues!, falses!, similar!, default_eltype # Convenience functions
export unsafe_zeros!, unsafe_ones!, unsafe_similar! # Unsafe convenience functions
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!
Expand Down
31 changes: 29 additions & 2 deletions src/acquire.jl
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,29 @@
@inline allocate_vector(::AbstractTypedPool{T,Vector{T}}, n::Int) where {T} =
Vector{T}(undef, n)

# BitTypedPool allocates BitVector (used when acquiring with Bit type)
@inline allocate_vector(::BitTypedPool, n::Int) = BitVector(undef, n)

# Bit type returns Bool element type for fill operations (zero/one)
@inline Base.zero(::Type{Bit}) = false
@inline Base.one(::Type{Bit}) = true

# 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}
unsafe_wrap(Array{T,N}, pointer(flat_view), dims)
end

# BitTypedPool cannot use unsafe_wrap - throw clear error
# Called from _unsafe_acquire_impl! dispatches for Bit type
@noinline function _throw_bit_unsafe_error()
throw(ArgumentError(
"unsafe_acquire!(pool, Bit, ...) is not supported. " *
"BitArray stores data in immutable chunks::Vector{UInt64} that cannot be wrapped with unsafe_wrap. " *
"Use acquire!(pool, Bit, ...) instead, which returns a view."
))
end

# ==============================================================================
# Helper: Overflow-Safe Product
# ==============================================================================
Expand Down Expand Up @@ -228,6 +245,11 @@ end
# Similar-style
@inline _unsafe_acquire_impl!(pool::AbstractArrayPool, x::AbstractArray) = _unsafe_acquire_impl!(pool, eltype(x), size(x))

# Bit type: unsafe_acquire! not supported (throw clear error early)
@inline _unsafe_acquire_impl!(::AbstractArrayPool, ::Type{Bit}, ::Int) = _throw_bit_unsafe_error()
@inline _unsafe_acquire_impl!(::AbstractArrayPool, ::Type{Bit}, ::Vararg{Int,N}) where {N} = _throw_bit_unsafe_error()
@inline _unsafe_acquire_impl!(::AbstractArrayPool, ::Type{Bit}, ::NTuple{N,Int}) where {N} = _throw_bit_unsafe_error()

# ==============================================================================
# Acquisition API (User-facing with untracked marking)
# ==============================================================================
Expand Down Expand Up @@ -428,9 +450,14 @@ const _acquire_array_impl! = _unsafe_acquire_impl!
@inline unsafe_acquire!(::DisabledPool{:cpu}, ::Type{T}, dims::NTuple{N,Int}) where {T,N} = Array{T,N}(undef, dims)
@inline unsafe_acquire!(::DisabledPool{:cpu}, x::AbstractArray) = similar(x)

# --- acquire! for DisabledPool{:cpu} with Bit type (returns BitArray) ---
@inline acquire!(::DisabledPool{:cpu}, ::Type{Bit}, n::Int) = BitVector(undef, n)
@inline acquire!(::DisabledPool{:cpu}, ::Type{Bit}, dims::Vararg{Int,N}) where {N} = BitArray{N}(undef, dims)
@inline acquire!(::DisabledPool{:cpu}, ::Type{Bit}, dims::NTuple{N,Int}) where {N} = BitArray{N}(undef, dims)

# --- Generic DisabledPool fallbacks (unknown backend → error) ---
@inline acquire!(p::DisabledPool{B}, args...) where {B} = _throw_backend_not_loaded(B)
@inline unsafe_acquire!(p::DisabledPool{B}, args...) where {B} = _throw_backend_not_loaded(B)
@inline acquire!(::DisabledPool{B}, _args...) where {B} = _throw_backend_not_loaded(B)
@inline unsafe_acquire!(::DisabledPool{B}, _args...) where {B} = _throw_backend_not_loaded(B)

# --- _impl! delegators for DisabledPool (macro transformation support) ---
# Called when: USE_POOLING=true + @maybe_with_pool + MAYBE_POOLING_ENABLED[]=false
Expand Down
114 changes: 114 additions & 0 deletions src/convenience.jl
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,12 @@ end
_zeros_impl!(pool, default_eltype(pool), dims...)
end

# Bit type specialization: zeros!(pool, Bit, ...) delegates to falses!(pool, ...)
@inline zeros!(pool::AbstractArrayPool, ::Type{Bit}, dims::Vararg{Int,N}) where {N} = falses!(pool, dims...)
@inline zeros!(pool::AbstractArrayPool, ::Type{Bit}, dims::NTuple{N,Int}) where {N} = falses!(pool, dims)
@inline _zeros_impl!(pool::AbstractArrayPool, ::Type{Bit}, dims::Vararg{Int,N}) where {N} = _falses_impl!(pool, dims...)
@inline _zeros_impl!(pool::AbstractArrayPool, ::Type{Bit}, dims::NTuple{N,Int}) where {N} = _falses_impl!(pool, dims)

# ==============================================================================
# ones! - Acquire one-initialized arrays from pool
# ==============================================================================
Expand Down Expand Up @@ -150,6 +156,92 @@ end
_ones_impl!(pool, default_eltype(pool), dims...)
end

# Bit type specialization: ones!(pool, Bit, ...) delegates to trues!(pool, ...)
@inline ones!(pool::AbstractArrayPool, ::Type{Bit}, dims::Vararg{Int,N}) where {N} = trues!(pool, dims...)
@inline ones!(pool::AbstractArrayPool, ::Type{Bit}, dims::NTuple{N,Int}) where {N} = trues!(pool, dims)
@inline _ones_impl!(pool::AbstractArrayPool, ::Type{Bit}, dims::Vararg{Int,N}) where {N} = _trues_impl!(pool, dims...)
@inline _ones_impl!(pool::AbstractArrayPool, ::Type{Bit}, dims::NTuple{N,Int}) where {N} = _trues_impl!(pool, dims)

# ==============================================================================
# trues! - Acquire BitArray filled with true from pool
# ==============================================================================

"""
trues!(pool, dims...) -> BitArray view
trues!(pool, dims::Tuple) -> BitArray view

Acquire a bit-packed boolean array filled with `true` from the pool.

Equivalent to Julia's `trues(dims...)` but using pooled memory.
Uses ~8x less memory than `ones!(pool, Bool, dims...)`.

## Example
```julia
@with_pool pool begin
bv = trues!(pool, 100) # BitVector-backed view, all true
bm = trues!(pool, 10, 10) # BitMatrix-backed reshaped array
end
```

See also: [`falses!`](@ref), [`ones!`](@ref), [`acquire!`](@ref)
"""
@inline function trues!(pool::AbstractArrayPool, dims::Vararg{Int,N}) where {N}
_mark_untracked!(pool)
_trues_impl!(pool, dims...)
end
@inline function trues!(pool::AbstractArrayPool, dims::NTuple{N,Int}) where {N}
_mark_untracked!(pool)
_trues_impl!(pool, dims...)
end

# Internal implementation (for macro transformation)
@inline function _trues_impl!(pool::AbstractArrayPool, dims::Vararg{Int,N}) where {N}
arr = _acquire_impl!(pool, Bit, dims...)
fill!(arr, true)
arr
end
@inline _trues_impl!(pool::AbstractArrayPool, dims::NTuple{N,Int}) where {N} = _trues_impl!(pool, dims...)

# ==============================================================================
# falses! - Acquire BitArray filled with false from pool
# ==============================================================================

"""
falses!(pool, dims...) -> BitArray view
falses!(pool, dims::Tuple) -> BitArray view

Acquire a bit-packed boolean array filled with `false` from the pool.

Equivalent to Julia's `falses(dims...)` but using pooled memory.
Uses ~8x less memory than `zeros!(pool, Bool, dims...)`.

## Example
```julia
@with_pool pool begin
bv = falses!(pool, 100) # BitVector-backed view, all false
bm = falses!(pool, 10, 10) # BitMatrix-backed reshaped array
end
```

See also: [`trues!`](@ref), [`zeros!`](@ref), [`acquire!`](@ref)
"""
@inline function falses!(pool::AbstractArrayPool, dims::Vararg{Int,N}) where {N}
_mark_untracked!(pool)
_falses_impl!(pool, dims...)
end
@inline function falses!(pool::AbstractArrayPool, dims::NTuple{N,Int}) where {N}
_mark_untracked!(pool)
_falses_impl!(pool, dims...)
end

# Internal implementation (for macro transformation)
@inline function _falses_impl!(pool::AbstractArrayPool, dims::Vararg{Int,N}) where {N}
arr = _acquire_impl!(pool, Bit, dims...)
fill!(arr, false)
arr
end
@inline _falses_impl!(pool::AbstractArrayPool, dims::NTuple{N,Int}) where {N} = _falses_impl!(pool, dims...)

# ==============================================================================
# similar! - Acquire arrays with same type/size as template
# ==============================================================================
Expand Down Expand Up @@ -477,6 +569,18 @@ end
@inline ones!(::DisabledPool{:cpu}, ::Type{T}, dims::NTuple{N,Int}) where {T,N} = ones(T, dims...)
@inline ones!(p::DisabledPool{:cpu}, dims::NTuple{N,Int}) where {N} = ones(default_eltype(p), dims...)

# --- zeros!/ones! for DisabledPool{:cpu} with Bit type (returns BitArray) ---
@inline zeros!(::DisabledPool{:cpu}, ::Type{Bit}, dims::Vararg{Int,N}) where {N} = falses(dims...)
@inline zeros!(::DisabledPool{:cpu}, ::Type{Bit}, dims::NTuple{N,Int}) where {N} = falses(dims...)
@inline ones!(::DisabledPool{:cpu}, ::Type{Bit}, dims::Vararg{Int,N}) where {N} = trues(dims...)
@inline ones!(::DisabledPool{:cpu}, ::Type{Bit}, dims::NTuple{N,Int}) where {N} = trues(dims...)

# --- trues!/falses! for DisabledPool{:cpu} ---
@inline trues!(::DisabledPool{:cpu}, dims::Vararg{Int,N}) where {N} = trues(dims...)
@inline trues!(::DisabledPool{:cpu}, dims::NTuple{N,Int}) where {N} = trues(dims...)
@inline falses!(::DisabledPool{:cpu}, dims::Vararg{Int,N}) where {N} = falses(dims...)
@inline falses!(::DisabledPool{:cpu}, dims::NTuple{N,Int}) where {N} = falses(dims...)

# --- similar! for DisabledPool{:cpu} ---
@inline similar!(::DisabledPool{:cpu}, x::AbstractArray) = similar(x)
@inline similar!(::DisabledPool{:cpu}, x::AbstractArray, ::Type{T}) where {T} = similar(x, T)
Expand Down Expand Up @@ -504,6 +608,8 @@ end
# --- Generic DisabledPool fallbacks (unknown backend → error) ---
@inline zeros!(p::DisabledPool{B}, args...) where {B} = _throw_backend_not_loaded(B)
@inline ones!(p::DisabledPool{B}, args...) where {B} = _throw_backend_not_loaded(B)
@inline trues!(p::DisabledPool{B}, args...) where {B} = _throw_backend_not_loaded(B)
@inline falses!(p::DisabledPool{B}, args...) where {B} = _throw_backend_not_loaded(B)
@inline similar!(p::DisabledPool{B}, args...) where {B} = _throw_backend_not_loaded(B)
@inline unsafe_zeros!(p::DisabledPool{B}, args...) where {B} = _throw_backend_not_loaded(B)
@inline unsafe_ones!(p::DisabledPool{B}, args...) where {B} = _throw_backend_not_loaded(B)
Expand All @@ -530,6 +636,14 @@ end
@inline _ones_impl!(p::DisabledPool, ::Type{T}, dims::NTuple{N,Int}) where {T,N} = ones!(p, T, dims)
@inline _ones_impl!(p::DisabledPool, dims::NTuple{N,Int}) where {N} = ones!(p, dims)

# --- _trues_impl! ---
@inline _trues_impl!(p::DisabledPool, dims::Vararg{Int,N}) where {N} = trues!(p, dims...)
@inline _trues_impl!(p::DisabledPool, dims::NTuple{N,Int}) where {N} = trues!(p, dims)

# --- _falses_impl! ---
@inline _falses_impl!(p::DisabledPool, dims::Vararg{Int,N}) where {N} = falses!(p, dims...)
@inline _falses_impl!(p::DisabledPool, dims::NTuple{N,Int}) where {N} = falses!(p, dims)

# --- _similar_impl! ---
@inline _similar_impl!(p::DisabledPool, x::AbstractArray) = similar!(p, x)
@inline _similar_impl!(p::DisabledPool, x::AbstractArray, ::Type{T}) where {T} = similar!(p, x, T)
Expand Down
13 changes: 13 additions & 0 deletions src/macros.jl
Original file line number Diff line number Diff line change
Expand Up @@ -826,6 +826,9 @@ function _extract_acquire_types(expr, target_pool, types=Set{Any}())
# acquire!(pool, x) - similar-style form
push!(types, Expr(:call, :eltype, expr.args[3]))
end
# trues!/falses! (always uses Bit type)
elseif fn in (:trues!, :falses!) || fn_name in (:trues!, :falses!)
push!(types, :Bit)
# zeros!/ones!/unsafe_zeros!/unsafe_ones!
elseif fn in (:zeros!, :ones!, :unsafe_zeros!, :unsafe_ones!) || fn_name in (:zeros!, :ones!, :unsafe_zeros!, :unsafe_ones!)
if nargs >= 3
Expand Down Expand Up @@ -1034,6 +1037,8 @@ const _ACQUIRE_IMPL_REF = GlobalRef(@__MODULE__, :_acquire_impl!)
const _UNSAFE_ACQUIRE_IMPL_REF = GlobalRef(@__MODULE__, :_unsafe_acquire_impl!)
const _ZEROS_IMPL_REF = GlobalRef(@__MODULE__, :_zeros_impl!)
const _ONES_IMPL_REF = GlobalRef(@__MODULE__, :_ones_impl!)
const _TRUES_IMPL_REF = GlobalRef(@__MODULE__, :_trues_impl!)
const _FALSES_IMPL_REF = GlobalRef(@__MODULE__, :_falses_impl!)
const _SIMILAR_IMPL_REF = GlobalRef(@__MODULE__, :_similar_impl!)
const _UNSAFE_ZEROS_IMPL_REF = GlobalRef(@__MODULE__, :_unsafe_zeros_impl!)
const _UNSAFE_ONES_IMPL_REF = GlobalRef(@__MODULE__, :_unsafe_ones_impl!)
Expand All @@ -1057,6 +1062,10 @@ function _transform_acquire_calls(expr, pool_name)
expr = Expr(:call, _ZEROS_IMPL_REF, expr.args[2:end]...)
elseif fn == :ones!
expr = Expr(:call, _ONES_IMPL_REF, expr.args[2:end]...)
elseif fn == :trues!
expr = Expr(:call, _TRUES_IMPL_REF, expr.args[2:end]...)
elseif fn == :falses!
expr = Expr(:call, _FALSES_IMPL_REF, expr.args[2:end]...)
elseif fn == :similar!
expr = Expr(:call, _SIMILAR_IMPL_REF, expr.args[2:end]...)
elseif fn == :unsafe_zeros!
Expand All @@ -1076,6 +1085,10 @@ function _transform_acquire_calls(expr, pool_name)
expr = Expr(:call, _ZEROS_IMPL_REF, expr.args[2:end]...)
elseif qn == QuoteNode(:ones!)
expr = Expr(:call, _ONES_IMPL_REF, expr.args[2:end]...)
elseif qn == QuoteNode(:trues!)
expr = Expr(:call, _TRUES_IMPL_REF, expr.args[2:end]...)
elseif qn == QuoteNode(:falses!)
expr = Expr(:call, _FALSES_IMPL_REF, expr.args[2:end]...)
elseif qn == QuoteNode(:similar!)
expr = Expr(:call, _SIMILAR_IMPL_REF, expr.args[2:end]...)
elseif qn == QuoteNode(:unsafe_zeros!)
Expand Down
Loading