MutatingOrNot
Documentation for MutatingOrNot.
MutatingOrNot.dryrunMutatingOrNot.voidMutatingOrNot.ArrayAllocatorMutatingOrNot.BasicDryRunMutatingOrNot.BasicVoidMutatingOrNot.DryRunMutatingOrNot.SmartAllocatorMutatingOrNot.VoidMutatingOrNot.has_dryrunMutatingOrNot.mallocMutatingOrNot.malloc_voidMutatingOrNot.mfreeMutatingOrNot.mfreeMutatingOrNot.set_dryrun
MutatingOrNot.dryrun — Constant
dryrun is the only instance of the singleton type BasicDryRun. When passed as an output argument, it is meant to signal that one wants to allocate that output argument, but not to do actual work. Indeed:
has_dryrun(dryrun) == trueSee void and has_dryrun.
MutatingOrNot.void — Constant
void is the only instance of the singleton type BasicVoid <: Void. When passed as an output argument, it is meant to signal that this argument is not allocated, and needs to be. Moreover:
has_dryrun(void) == false
set_dryrun(void) == dryrunMutatingOrNot.ArrayAllocator — Type
Parent type for array allocators. Array allocators can be passed as output arguments instead of preallocated arrays needed for intermediate computations. For instance:
function myfun1(tmp::Union{AbstractArray, ArrayAllocator}, input)
x = malloc(tmp, ...) # allocate temporary array y, as with `similar(...)`
...
ret = ... # do some computation that needs `x` as scratch space
return ret, x
end
alloc = ... # create allocator
ret0, x = myfun1(alloc, input0) # allocates
ret1, _ = myfun1(x, input1) # `x` is an array => should not allocateThe point of x being returned is to use it in subsequent calls and (potentially) avoid new allocations. A slightly different pattern is:
function myfun1(tmp::Union{AbstractArray, ArrayAllocator}, input)
x = malloc(tmp, ...) # allocate temporary array y, as with `similar(...)`
...
ret = ... # do some computation that needs `x` as scratch space
x = mfree(tmp, x) # `free` array x, in a sense depending on tmp
return ret, x
end
alloc = ... # create allocator
ret0, tmp = myfun1(alloc, input0) # allocates
ret1, _ = myfun1(tmp, input1) # `tmp` may be an array or an array allocatorThe precise behavior of mfree depends on the concrete type of tmp. mfree(tmp, x) may return the array x or tmp itself. Allocators are allowed to reuse memory passed to mfree for subsequent calls to malloc. Therefore an array must not be read/written after being passed to mfree.
Beyond arrays, nested (named) tuples arrays are supported. For this, the following behavior is implemented whenever v::ArrayAllocator
(; x, y, z) = tmp # results in `x==tmp` etc.
x, y, z = tmp # results in `x==tmp` etc.
@. tmp = expr # returns `@. expr`This enables the following, more advanced pattern:
function example(tmp::Union{AbstractArray, ArrayAllocator}, input)
ret1, x = myfun1(tmp.x, input) # uses `mfree`
ret2, y = myfun1(tmp.y, input) # uses `mfree`
# we are not allowed to read/write `x` or `y` here since they have been freed
# for instance `tmp` may have reused the memory allocated for `x`
# when allocating `y`, so that `x` and `y` refer to the same memory !
ret = ... # do some computation with ret1 and ret2
return ret, mfree(tmp, (; x,y))
end
alloc = ... # create allocator
ret0, tmp = myfun1(alloc, input0) # allocates
ret1, _ = myfun1(tmp, input1) # `tmp` may be a named tuple of arrays, or an array allocatorSee malloc, mfree , set_dryrun and has_dryrun.
MutatingOrNot.BasicDryRun — Type
struct BasicDryRun <: DryRun endSee DryRun.
MutatingOrNot.DryRun — Type
abstract type DryRun<:Void endInstances v of types subtyping DryDryn, especially dryrun, when passed as an output argument, are meant to signal that this argument needs to be allocated, but that no actual computation should take place. Especially:
has_dryrun(::DryRun) = trueSee has_dryrun.
MutatingOrNot.SmartAllocator — Method
smart = SmartAllocator()Return a smart allocator smart, with the following behavior:
- internally,
smartmaintains a store tracking previously allocated arrays, marked as eitherbusyorfree. y = malloc(smart, args...)searches that store for an array with the appropriate eltype and shape. If this fails, it allocates one withsimilar(args...). Either way,yis marked asbusyin the store.mfree(smart, y)keepsyin the store and marks it as free, so that a later call tomalloccan reuse it- any
mfreemust have a correspondingmalloc mfree(smart)marks all arrays in the store asfreebut does not actually free anything.- only
empty!(smart)actually empties the store, allowing Julia's garbage collector to act.
Note of caution:
- arrays from the store are reused only if they have the exact same eltype and shape as requested by
malloc. - to avoid runaway memory usage, any
mallocmust have a correspondingmfree - this allocator is smart only if the eltype and shape requested via
mallocbelong to a small set of possibilities.
MutatingOrNot.Void — Type
abstract type Void <: ArrayAllocator endInstances of types subtyping Void, especially void, when passed as an output argument, are meant to signal that this argument is not allocated, and needs to be. The aim is to implement both mutating and non-mutating styles in a single place, while facilitating the pre-allocation of output arguments before calling the mutating, non-allocating variant.
More specifically:
malloc(::Void, args...) = similar(args...)
mfree(::Void, x) = xSee ArrayAllocator, malloc, mfree , set_dryrun and has_dryrun. See also void, dryrun
MutatingOrNot.has_dryrun — Method
When tmp::ArrayAllocator, has_dryrun(tmp)==true signals that only allocations should take place, but not actual work (computations). Furthermore:
has_dryrun(x) == any(has_dryrun, x) # if `x` is a (named) tuple
has_dryrun(x) == false # if `x` is an arrayUse it to avoid computations when only allocations are desired. Example:
function f!(tmp, x, y)
# allocations, if needed
a = malloc(tmp.a, x) # same type and shape as `x`
b = malloc(tmp.b, y) # same type and shape as `y`
# early exit, if requested
has_dryrun(x) && return (; a, b)
# computations
a = @. a = y*y
b = @. b = exp(z)
return (; a, b)
endIn the above example,
- the special properties of
x_::Void(seeVoid) are used x = f!(void, y)is the non-mutating variant off!x = f!(dryrun, y)just allocates x, without performing actual workx = f!(x, y)mutates the pre-allocated x (non-allocating)
MutatingOrNot.malloc — Method
x = malloc(tmp, args...)When tmp::ArrayAllocator, return array x, similarly to similar(args...). The allocator tmp may provide more or less sophisticated allocation strategies.
Otherwise, especially when tmp is an array or a (nested) (named) tuple thereof, return tmp itself. The goal is to allocate x only when a pre-allocated tmp is not provided.
See void and SmartAllocator.
MutatingOrNot.malloc_void — Method
x = malloc_void(args...)malloc_void(args...) defaults to similar(y...). Furthermore the single-argument malloc_void(y) applies recursively to tuples and named tuples. Contrary to similar, it is not possible to specify eltype or dims in this recursive variant.
MutatingOrNot.mfree — Method
mfree(tmp, x)Free array x, which was previously allocated by malloc(tmp, ...). Whether anything is actually done depends on the allocator tmp. See void and SmartAllocator.
MutatingOrNot.mfree — Method
mfree(tmp)Free allocator tmp. Whether anything is actually done depends on the allocator tmp. See void and SmartAllocator.
MutatingOrNot.set_dryrun — Function
dryrun_tmp = set_dryrun(tmp)Return allocator dryrun_tmp with the same behavior as tmp except that has_dryrun(dryrun_tmp)==true.