JuliaSIMD / JuliaSIMD/StrideArraysCore.jl
Removing allocations for repeated use of object_and_preserve
- Dominant language
- Julia
- Stars
- 19
- Forks
- 10
- PR merge metrics
- No merged PRs in 30d
Description
When using `CheapThreads.jl`, I get allocations when new `Reference`s are created in `object_and_preserve`. This makes sense, but I'd really like to do away with this is possible as my aim is to use `CheapThreads.jl` within `DifferentialEquations.jl` and I don't want the allocs to build up. In my case the arg that is being referenced is a `struct`, but you also get allocations for other `!isbits` types, e.g.:
```Julia
julia> f1(arg) = @time StrideArraysCore.object_and_preserve(arg)
julia> f1(1)
0.000000 seconds
(1, nothing)
julia> f1("string")
0.000000 seconds (1 allocation: 16 bytes)
(StrideArraysCore.Reference{String}("string"), StrideArraysCore.Reference{String}("string"))
julia> struct TestStruct
a::Vector{Int64}
end
julia> ts = TestStruct([1])
TestStruct([1])
julia> f1(ts)
0.000000 seconds (1 allocation: 16 bytes)
(StrideArraysCore.Reference{TestStruct}(TestStruct([1])), StrideArraysCore.Reference{TestStruct}(TestStruct([1])))
```
I can see two possible solutions:
#### 1. Allow users to create their own `Reference`s
This just needs `object_and_preserve` to be extended like
```Julia
@inline object_and_preserve(r::Reference) = (r, r)
```
When added, it works like so:
```Julia
julia> ref = StrideArraysCore.Reference("string")
StrideArraysCore.Reference{String}("string")
julia> f1(ref)
0.000000 seconds
(StrideArraysCore.Reference{String}("string"), StrideArraysCore.Reference{String}("string"))
julia> ref = StrideArraysCore.Reference(ts)
StrideArraysCore.Reference{TestStruct}(TestStruct([1]))
julia> f1(ref)
0.000000 seconds
(StrideArraysCore.Reference{TestStruct}(TestStruct([1])), StrideArraysCore.Reference{TestStruct}(TestStruct([1])))
```
This seems to work fine with `batch` from `CheapThreads.jl`.
#### 2. Encourage users to extend `object_and_preserve`, `store!` and `load` for their `struct`s
For example:
```Julia
@inline function ThreadingUtilities.load(p::Ptr{UInt}, ::Type{TestMutableStruct}, i)
i, ref = ThreadingUtilities.load(p, Ptr{TestMutableStruct}, i)
i, Base.unsafe_pointer_to_objref(ref)::TestMutableStruct
end
@inline function ThreadingUtilities.store!(p::Ptr{UInt}, r::TestMutableStruct, i)
ThreadingUtilities.store!(p + i, reinterpret(UInt, pointer_from_objref(r)))
i + sizeof(UInt)
end
@inline StrideArraysCore.object_and_preserve(r::TestMutableStruct) = (r, r)
```
This seems to be fine with `CheapThreads`, but I don't know if there are implications of doing this.
```Julia
julia> arg_test(args, start, stop) = nothing
julia> mutable struct TestMutableStruct
a::String
end
julia> tms = TestMutableStruct("a")
TestMutableStruct("a")
julia> u = zeros(100)
...
julia> @time batch(arg_test, (10, 8), u, tms)
0.000003 seconds
```
The above extensions only work for mutable structs because of `pointer_from_objref`.
I'm interested to know what people's thoughts are on the best way forward for this!
Contributor guide
No contributing guide indexed for this repository
Assessment
This issue has not been assessed yet.