Skip to content

Commit 02e4ad4

Browse files
authored
Merge pull request #3 from longemen3000/dev
reimplementation of the internals
2 parents 5e2668c + d316f75 commit 02e4ad4

File tree

5 files changed

+110
-146
lines changed

5 files changed

+110
-146
lines changed

README.md

+3-4
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ This package maybe can help you!
1414

1515
This package works with implace functions of the form: `f(out,x)`, where:
1616
1. `eltype(x) == eltype(out)`
17-
2. `x` is of type Array.
17+
2. `x` is of type Array,Dict,SparseVector,or SparseArray
1818
3. by default, the caches are not thread-safe or async safe. future releases will add special cached types to deal with this. as a workaround, you can try creating new cached functions instances using `deepcopy(f)`
1919

2020
help on easing those limits is appreciated.
@@ -50,13 +50,12 @@ julia> f
5050
cached version of f! (function with 2 cached methods)
5151
julia> calls(f)
5252
5
53-
julia> methods(f)
53+
julia> cached_methods(f)
5454
IdDict{DataType,Function} with 2 entries:
5555
Float64 => #198
5656
Float32 => #198
5757
```
58-
All the cached methods are stored in `methods(f)`. you can take one and use it if you want. each method is a closure
59-
with the specific cache created. and if the cache doesn't exists, it's created on the fly during runtime.
58+
A dict with all cached closures for each type is stored in `cached_methods(f)`. you can take one and use it if you want. If the cache doesn't exists, it's created on the fly during runtime.
6059

6160
What happens if i don't want to allocate during runtime?, The solution: use `allocate!(f,Type)`
6261

src/CachedFunctions.jl

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
module CachedFunctions
22
using SparseArrays
3-
export CachedFunction, input, output, evaluate, evaluate!, calls, allocate!
3+
export CachedFunction, input, output, evaluate, evaluate!, calls, allocate!, cached_methods
44

55
include("storage_constructors.jl")
66
include("cached_function.jl")

src/cached_function.jl

+50-45
Original file line numberDiff line numberDiff line change
@@ -4,66 +4,72 @@
44

55

66

7-
struct CachedFunction{F,S,I,O,L}
7+
struct CachedFunction{F,I,O,S}
88
f!::F
9-
storage_type::S ##by default, a plain array
10-
in_size::SIZE{I} #a ntuple of ints
11-
out_size::SIZE{O} #a ntuple of ints
9+
input::I #prototype info for input
10+
output::O #prototype info for input
1211
x_cache::IdDict{DataType, S}
1312
closures::IdDict{DataType, Function}
1413
f_calls::Vector{Int64}
15-
lock::L
14+
lock::ReentrantLock
1615
end
1716

1817

1918

2019

2120

22-
function CachedFunction(f!,_in::SIZE,out::SIZE,stype=Array)
23-
storage_type = type_constructor(stype,out)
24-
in_size = _in
25-
out_size = out
26-
x_cache = IdDict{DataType,type_constructor(stype,in)}()
21+
22+
function CachedFunction(f!,_in::SIZE{N1},_out::SIZE{N2}) where {N1,N2}
23+
input = Prototype(Array{Float64,N1},_in)
24+
output = Prototype(Array{Float64,N2},out)
25+
x_cache = IdDict{DataType,Array{Any,N1}}()
26+
closures = IdDict{DataType, Function}()
27+
f_calls = [0]
28+
lock = ReentrantLock()
29+
return CachedFunction(f!,input,output,x_cache,closures,f_calls,lock)
30+
end
31+
32+
33+
function CachedFunction(f!,_in::T1,out::T2) where {T1,T2}
34+
input = prototype(_in)
35+
output = prototype(out)
36+
x_cache = IdDict{DataType,T1.name.wrapper}()
2737
closures = IdDict{DataType, Function}()
2838
f_calls = [0]
2939
lock = ReentrantLock()
30-
return CachedFunction(f!,storage_type,in_size,out_size,x_cache,closures,f_calls,lock)
40+
return CachedFunction(f!,input,output,x_cache,closures,f_calls,lock)
3141
end
3242

43+
3344
#creates an instance of the cache, using the storage_type and the size. uses type constructor
3445

3546
#generates an array of _eltype in
3647

3748

38-
function make_closure!(f!,stype,_eltype,_size)
39-
out_cache = make_cache!(f!,stype,_eltype,_size)
49+
function make_closure!(f!,prototype,elem_type)
50+
out_cache = make_cache!(f!,prototype,elem_type)
4051
return x -> f!(out_cache, x)
4152
end
4253

4354
eval_f(f::F, x) where {F} = f(x)
4455

45-
function (cfn::CachedFunction{F,O})(x) where {F,O}
46-
X = _eltype(x)
47-
out = cfn.out_size
48-
f = get!(() -> make_closure!(cfn.f!,O,X,out), cfn.closures, X)
56+
function (cfn::CachedFunction{F})(x) where {F}
57+
X = valtype(x)
58+
f = get!(() -> make_closure!(cfn.f!, cfn.output, X),cfn.closures,X)
4959
cfn.f_calls[1] +=1
5060
return eval_f(f, x)
5161
end
5262

53-
function allocate!(cfn::CachedFunction{F,O},x) where {F,O}
54-
X = _eltype(x)
55-
out = cfn.out_size
56-
_in = cfn.in_size
57-
get!(cfn.x_cache,X, x) #if x is not of the correct type, then it will throw an error.
58-
get!(() -> make_closure!(cfn.f!,O,X,out), cfn.closures, X)
63+
function allocate!(cfn::CachedFunction{F},x) where {F}
64+
X = valtype(x)
65+
get!(() -> x,cfn.x_cache,X)
66+
get!(() -> make_closure!(cfn.f!, cfn.output, X),cfn.closures,X)
5967
return nothing
6068
end
6169

62-
function allocate!(cfn::CachedFunction{F,O},x::Type{X}) where {F,O,X}
63-
out = cfn.out_size
64-
_in = _size(cfn.in_size)
65-
get!(cfn.x_cache,X, make_cache!(cfn.f!,O,X,_in))
66-
get!(() -> make_closure!(cfn.f!,O,X,out), cfn.closures, X)
70+
function allocate!(cfn::CachedFunction{F},x::Type{X}) where {F,X}
71+
get!(() -> make_cache!(cfn.f!, cfn.input, X),cfn.x_cache,X)
72+
get!(() -> make_closure!(cfn.f!, cfn.output, X),cfn.closures,X)
6773
return nothing
6874
end
6975
#@btime c($[1.0,2.0])
@@ -85,21 +91,21 @@ returns the output cache stored in ´f´
8591
Returns `f(x)`, it doesn' t store the input.
8692
if your function has only one type cached, you can get the output cache without adding the type.
8793
"""
88-
function output(f::CachedFunction)
89-
if length(c.closures) == 1
90-
return last(first(c.closures)).out_cache
91-
elseif length(c.closures) == 0
94+
function output(cfn::CachedFunction)
95+
if length(cfn.closures) == 1
96+
return last(first(cfn.closures)).out_cache
97+
elseif length(cfn.closures) == 0
9298
error("there aren't any output caches allocated in cached function")
9399
else
94100
error("there are more than one type of output cache stored in cached function.")
95101
end
96102
end
97103

98-
function output(f::CachedFunction,x::Type{T}) where T
99-
if length(c.closures) == 0
104+
function output(cfn::CachedFunction,x::Type{T}) where T
105+
if length(cfn.closures) == 0
100106
error("there aren't any output caches allocated in cached function.")
101107
else
102-
res = get(c.closures,T,nothing)
108+
res = get(cfn.closures,T,nothing)
103109
if isnothing(res)
104110
TT = T.name.wrapper
105111
error("there aren't any output caches of type $TT in cached function.")
@@ -109,17 +115,17 @@ function output(f::CachedFunction,x::Type{T}) where T
109115
end
110116
end
111117

112-
output(f::CachedFunction,x) = output(f,_eltype(x))
118+
output(f::CachedFunction,x) = output(f,valtype(x))
113119

114120

115121

116122
###input####
117123

118-
function input(f::CachedFunction,x::Type{T}) where T
119-
if length(c.x_cache) == 0
124+
function input(cfn::CachedFunction,x::Type{T}) where T
125+
if length(cfn.x_cache) == 0
120126
error("there aren't any input caches allocated in cached function")
121127
else
122-
res = get(c.x_cache,T,nothing)
128+
res = get(cfn.x_cache,T,nothing)
123129
if isnothing(res)
124130
TT = T.name.wrapper
125131
error("there aren't any input caches of type $TT in cached function.")
@@ -129,7 +135,7 @@ function input(f::CachedFunction,x::Type{T}) where T
129135
end
130136
end
131137

132-
input(f::CachedFunction,x) = input(f,_eltype(x))
138+
input(f::CachedFunction,x) = input(f,valtype(x))
133139

134140

135141
####evaluate#####
@@ -152,20 +158,19 @@ Returns `f(x)` and stores the input value.
152158
153159
"""
154160
function evaluate!(cfn::CachedFunction,x)
155-
X = _eltype(x)
161+
X = valtype(x)
156162
out = cfn.out_size
157163
if haskey(cfn.x_cache,X)
158164
xx = get(cfn.x_cache,X,nothing)
159165
copyto!(xx,x)
160166
else
161167
xx = get!(cfn.x_cache,X,x)
162168
end
163-
f = get!(() -> make_closure!(cfn.f!, x,out), cfn.closures, X)
169+
f = get!(() -> make_closure!(cfn.f!, cfn.output, X),cfn.closures,X)
164170
cfn.f_calls[1] +=1
165-
return eval_f(f, xx)
171+
return eval_f(f, x)
166172
end
167173

168-
169174
function Base.show(io::IO, fn::CachedFunction)
170175
n_methods = length(fn.closures)
171176
_methods = n_methods == 1 ? "method" : "methods"
@@ -174,7 +179,7 @@ function Base.show(io::IO, fn::CachedFunction)
174179
end
175180

176181

177-
methods(f::CachedFunction) = f.closures
182+
cached_methods(f::CachedFunction) = f.closures
178183

179184

180185
#lock methods

src/storage_constructors.jl

+34-93
Original file line numberDiff line numberDiff line change
@@ -2,115 +2,56 @@
22
const SIZE = NTuple{N,Int} where N
33
const LENGTH = SIZE{1}
44

5-
#overload of size to always use tuples
6-
function _size(out::OUT) where OUT
7-
if Base.IteratorSize(OUT) == Base.HasLength() #linear struct
8-
return length(out)
9-
elseif typeof(Base.IteratorSize(OUT)) <: Base.HasShape #sized struct
10-
return size(out)
11-
else
12-
error("the size of the output is not known")
13-
end
14-
end
15-
16-
function _size(out::SIZE)
17-
return out
18-
end
19-
20-
function _size(out::Int)
21-
return (out,)
22-
end
23-
24-
25-
26-
#like eltype, but gives the value type with dicts
27-
_eltype(x) = eltype(x)
28-
function _eltype(x::T) where T<:AbstractDict{K,V} where {K,V}
29-
return V
30-
end
31-
32-
#provide errors when the type cannot conform to the size
33-
function _only1d_error(type)
34-
_type = type.name.wrapper
35-
return error("incorrect size provided. The $_type type can only have length.")
36-
end
37-
38-
#provide errors when the type cannot conform to the size
39-
function _only2d_error(type)
40-
_type = type.name.wrapper
41-
return error("incorrect size provided. The $_type type can only two dimensions.")
42-
end
435

6+
#prototype abstract, stores the storage type in S
7+
abstract type AbstractPrototype{S} end
448

459

46-
#used to generate an appropiate type constructor for storage
47-
#the storage should only depend on the element type and the size, nothing more, nothing less.
48-
49-
50-
51-
#instance of type instead of type
52-
type_constructor(storage_type,size::SIZE) = type_constructor(typeof(storage_type),size)
53-
54-
#array
55-
function type_constructor(storage_type::Type{T1},size::SIZE) where T1 <: AbstractArray{T2,T3} where {T2,T3}
56-
return storage_type.name.wrapper{T,prod(size)} where T
10+
#prototype stores the type to dispatch and the data necesary to create a new instance of other element types.
11+
struct Prototype{S,T}
12+
type::S
13+
data::T #a tuple to store associated data
5714
end
5815

59-
#sparse vector
60-
function type_constructor(storage_type::Type{T1},size::LENGTH) where T1 <: SparseVector
61-
return SparseVector{T,Int64} where T
62-
end
6316

64-
#sparse vector error with different sizes
65-
function type_constructor(storage_type::Type{T1},size::SIZE) where T1 <: SparseVector
66-
return _only1d_error(T1)
67-
end
17+
prototype(x::T) where T<:Array = Prototype(typeof(x),(eltype(x),size(x)))
18+
prototype(d::T) where T<:Dict = Prototype(typeof(d),(valtype(d),d.slots, d.keys, d.ndel, d.count, d.age, d.idxfloor, d.maxprobe))
19+
prototype(sv::T) where T<:SparseArrays.SparseVector = Prototype(typeof(sv),(eltype(sv),sv.n,sv.nzind))
20+
prototype(sm::T) where T<:SparseArrays.SparseMatrixCSC = Prototype(typeof(sm),(eltype(sm),size(sm),sm.rowval,sm.colptr))
6821

69-
#sparse matrix
70-
function type_constructor(storage_type::Type{T1},size::SIZE{2}) where T1 <: SparseMatrixCSC
71-
return SparseMatrixCSC{T,Int64} where T
22+
function make_cache!(f!,p::P,elem_type) where {P <: Prototype}
23+
return make_cache!(p.type,f!,p,elem_type)
7224
end
7325

74-
#sparse matrix error with different sizes
75-
function type_constructor(storage_type::Type{T1},size::SIZE) where T1 <: SparseMatrixCSC
76-
return _only2d_error(T1)
26+
function make_cache!(::Type{T1},f!,p::P,elem_type::Type{T2}) where {T1 <: Array, P <: Prototype, T2}
27+
s = p.data[2]
28+
n = length(s)
29+
return Array{T2,n}(undef,s)
7730
end
7831

79-
80-
#abstract dict
81-
function type_constructor(storage_type::Type{T1},size::LENGTH) where T1 <: AbstractDict{K,V} where {K,V}
82-
return storage_type.name.wrapper{K,T} where T
32+
#the approach here is more like Dictionaries.jl
33+
function make_cache!(::Type{T1},f!,p::P,elem_type::Type{T2}) where {T1 <: Dict, P <: Prototype, T2}
34+
t, slots, keys, ndel, count, age, idxfloor, maxprobe = p.data
35+
n = length(slots)
36+
K = eltype(keys)
37+
vals = Vector{T2}(undef,n)
38+
return Dict{K,T2}(slots,keys,vals,ndel,count,age,idxfloor,maxprobe)
8339
end
8440

85-
#abstract dict error
86-
function type_constructor(storage_type::Type{T1},size::SIZE) where T1 <: AbstractDict{K,V} where {K,V}
87-
return _only1d_error(T1)
41+
function make_cache!(::Type{T1},f!,p::P,elem_type::Type{T2}) where {T1 <: SparseArrays.SparseVector, P <: Prototype, T2,K,V}
42+
t,n,nzind = p.data
43+
nzval = Vector{T2}(undef,n)
44+
return SparseArrays.SparseVector{T2,eltype(nzind)}(n,nzind,nzval)
8845
end
8946

90-
91-
92-
93-
#make_cache! generates an instance of the object, given its storage type and size
94-
95-
#general constructor for arrays
96-
function make_cache!(f!,storage_type::T1,element_type,_size::SIZE) where T1 <: AbstractArray
97-
t = type_constructor(storage_type,_size){element_type}
98-
return t(undef,_size)
99-
end
100-
101-
#sparse matrix
102-
function make_cache!(f!,storage_type::T1,element_type,_size::SIZE{2}) where T1 <: SparseMatrixCSC
103-
return spzeros(storage_type,_size...)
47+
function make_cache!(::Type{T1},f!,p::P,elem_type::Type{T2}) where {T1 <: SparseArrays.SparseMatrixCSC, P <: Prototype, T2,K,V}
48+
t,s,rowval,colptr = p.data
49+
m,n = s
50+
l = length(rowval)
51+
t,n,nzind = p.data
52+
nzval = Vector{T2}(undef,l)
53+
return SparseArrays.SparseVector{T2,eltype(nzind)}(m,n,colptr,nzind,nzval)
10454
end
10555

10656

107-
#sparse vector
108-
function make_cache!(f!,storage_type::T1,element_type,_size::LENGTH) where T1 <: SparseVector
109-
return spzeros(storage_type,first(_size))
110-
end
11157

112-
#abstract dict
113-
function make_cache!(f!,storage_type::T1,element_type,_size::LENGTH) where T1 <: AbstractDict{K,V} where {K,V}
114-
dict = type_constructor(storage_type,_size){element_type}()
115-
sizehint!(dict,prod(_size))
116-
end

0 commit comments

Comments
 (0)