MutatingOrNot

Documentation for MutatingOrNot.

MutatingOrNot.dryrunConstant

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) == true

See void and has_dryrun.

source
MutatingOrNot.voidConstant

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) == dryrun

See Void, DryRun.

source
MutatingOrNot.ArrayAllocatorType

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 allocate

The 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 allocator

The 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 allocator

See malloc, mfree , set_dryrun and has_dryrun.

source
MutatingOrNot.DryRunType
abstract type DryRun<:Void end

Instances 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) = true

See has_dryrun.

source
MutatingOrNot.SmartAllocatorMethod
smart = SmartAllocator()

Return a smart allocator smart, with the following behavior:

  • internally, smart maintains a store tracking previously allocated arrays, marked as either busy or free.
  • y = malloc(smart, args...) searches that store for an array with the appropriate eltype and shape. If this fails, it allocates one with similar(args...). Either way, y is marked as busy in the store.
  • mfree(smart, y) keeps y in the store and marks it as free, so that a later call to malloc can reuse it
  • any mfree must have a corresponding malloc
  • mfree(smart) marks all arrays in the store as free but 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 malloc must have a corresponding mfree
  • this allocator is smart only if the eltype and shape requested via malloc belong to a small set of possibilities.
source
MutatingOrNot.VoidType
abstract type Void <: ArrayAllocator end

Instances 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) = x

See ArrayAllocator, malloc, mfree , set_dryrun and has_dryrun. See also void, dryrun

source
MutatingOrNot.has_dryrunMethod

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 array

Use 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)
end

In the above example,

  • the special properties of x_::Void (see Void) are used
  • x = f!(void, y) is the non-mutating variant of f!
  • x = f!(dryrun, y) just allocates x, without performing actual work
  • x = f!(x, y) mutates the pre-allocated x (non-allocating)
source
MutatingOrNot.mallocMethod
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.

source
MutatingOrNot.malloc_voidMethod
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.

source
MutatingOrNot.mfreeMethod
mfree(tmp)

Free allocator tmp. Whether anything is actually done depends on the allocator tmp. See void and SmartAllocator.

source
MutatingOrNot.set_dryrunFunction
dryrun_tmp = set_dryrun(tmp)

Return allocator dryrun_tmp with the same behavior as tmp except that has_dryrun(dryrun_tmp)==true.

source