Skip to content

Commit b059e34

Browse files
feat: add SmallVec
1 parent ab977fc commit b059e34

File tree

2 files changed

+130
-0
lines changed

2 files changed

+130
-0
lines changed

src/SymbolicUtils.jl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ import TaskLocalValues: TaskLocalValue
2525

2626
include("cache.jl")
2727
Base.@deprecate istree iscall
28+
29+
include("small_array.jl")
30+
2831
export istree, operation, arguments, sorted_arguments, iscall
2932
# Sym, Term,
3033
# Add, Mul and Pow

src/small_array.jl

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
"""
2+
$(TYPEDEF)
3+
4+
A mutable resizeable 3-length vector to implement non-allocating small vectors.
5+
6+
$(TYPEDFIELDS)
7+
"""
8+
mutable struct Backing{T} <: AbstractVector{T}
9+
"""
10+
Length of the buffer.
11+
"""
12+
len::Int
13+
x1::T
14+
x2::T
15+
x3::T
16+
17+
Backing{T}() where {T} = new{T}(0)
18+
Backing{T}(x1) where {T} = new{T}(1, x1)
19+
Backing{T}(x1, x2) where {T} = new{T}(2, x1, x2)
20+
Backing{T}(x1, x2, x3) where {T} = new{T}(3, x1, x2, x3)
21+
end
22+
23+
Base.size(x::Backing) = (x.len,)
24+
Base.isempty(x::Backing) = x.len == 0
25+
26+
"""
27+
$(TYPEDSIGNATURES)
28+
29+
Value to use when removing an element from a `Backing`. Used so stored entries can be
30+
GC'ed when removed.
31+
"""
32+
defaultval(::Type{T}) where {T <: Number} = zero(T)
33+
defaultval(::Type{Any}) = nothing
34+
35+
function Base.getindex(x::Backing, i::Int)
36+
@boundscheck 1 <= i <= x.len
37+
if i == 1
38+
x.x1
39+
elseif i == 2
40+
x.x2
41+
elseif i == 3
42+
x.x3
43+
end
44+
end
45+
46+
function Base.setindex!(x::Backing, v, i::Int)
47+
@boundscheck 1 <= i <= x.len
48+
if i == 1
49+
setfield!(x, :x1, v)
50+
elseif i == 2
51+
setfield!(x, :x2, v)
52+
elseif i == 3
53+
setfield!(x, :x3, v)
54+
end
55+
end
56+
57+
function Base.push!(x::Backing, v)
58+
x.len < 3 || throw(ArgumentError("`Backing` is full"))
59+
x.len += 1
60+
x[x.len] = v
61+
end
62+
63+
function Base.pop!(x::Backing{T}, v) where {T}
64+
x.len > 0 || throw(ArgumentError("Array is empty"))
65+
v = x[x.len]
66+
x[x.len] = defaultval(T)
67+
x.len -= 1
68+
v
69+
end
70+
71+
"""
72+
$(TYPEDSIGNATURES)
73+
74+
Whether the `Backing` is full.
75+
"""
76+
isfull(x::Backing) = x.len == 3
77+
78+
"""
79+
$(TYPEDSIGNATURES)
80+
81+
A small-buffer-optimized `AbstractVector`. Uses a `Backing` when the number of elements
82+
is within the size of `Backing`, and allocates a `V` when the number of elements exceed
83+
this limit.
84+
"""
85+
mutable struct SmallVec{T, V <: AbstractVector{T}} <: AbstractVector{T}
86+
data::Union{Backing{T}, V}
87+
88+
function SmallVec{T}(x::AbstractVector{T}) where {T}
89+
V = typeof(x)
90+
if length(x) < 4
91+
new{T, V}(Backing{T}(x...))
92+
else
93+
new{T, V}(x)
94+
end
95+
end
96+
97+
function SmallVec{T, V}() where {T, V}
98+
new{T, V}(Backing{T}())
99+
end
100+
101+
function SmallVec{T, V}(x::Union{Tuple, AbstractVector}) where {T, V}
102+
if length(x) <= 3
103+
new{T, V}(Backing{T}(x...))
104+
else
105+
new{T, V}(V(x isa Tuple ? collect(x) : x))
106+
end
107+
end
108+
end
109+
110+
Base.convert(::Type{SmallVec{T, V}}, x::V) where {T, V} = SmallVec{T}(x)
111+
Base.convert(::Type{SmallVec{T, V}}, x) where {T, V} = SmallVec{T}(V(x))
112+
Base.convert(::Type{SmallVec{T, V}}, x::SmallVec{T, V}) where {T, V} = x
113+
114+
Base.size(x::SmallVec) = size(x.data)
115+
Base.isempty(x::SmallVec) = isempty(x.data)
116+
Base.getindex(x::SmallVec, i::Int) = x.data[i]
117+
Base.setindex!(x::SmallVec, v, i::Int) = setindex!(x.data, v, i)
118+
119+
function Base.push!(x::SmallVec{T, V}, v) where {T, V}
120+
buf = x.data
121+
buf isa Backing{T} || return push!(buf::V, v)
122+
isfull(buf) || return push!(buf::Backing{T}, v)
123+
x.data = V(buf)
124+
return push!(x.data::V, v)
125+
end
126+
127+
Base.pop!(x::SmallVec) = pop!(x.data)

0 commit comments

Comments
 (0)