Table of Contents
When you call a function on one or more SIMD values, you expect the computation to be performed on every elements of its parameters. Sometimes, you may want to make the application of a given function dependent on some condition. Let's explore the functionalities EVE provides for this kind of task.
Explicit Selection
Let's say the function we want to write computes the product of two values a
and b
if a
is equal to b
and their difference otherwise.
The scalar code is looking like:
The SIMD version of this code can't use if
nor the ternary operator directly. The correct approach is to use the [if_else][doc_if_else] function.
The [if_else][doc_if_else] call explicitly requests we pass:
- the condition, i.e the comparison between
a
andb
- the value to use whenever an element of said condition evaluates to
true
, here the product ofa
andb
- the value to use whenever an element of said condition evaluates to
false
, here the difference ofa
andb
The expected result of this program is:
!!! WARNING Contrary to a if ... else
statement, [if_else][doc_if_else] will evaluates all its arguments before performing its selection even if potential short-cut can be applied later on.
Conditional Function Syntax
Let's define a safe_sqrt
function that computes the square root of its argument if it's positive or returns it unchanged otherwise. One can write:
This code is perfectly valid and will produce the correct result. However, it has some issues:
- the code looks like the important part is the test
- the code can't be optimized in case the current architecture support masked operations (i.e. AVX512)
EVE functions supports – whenever it makes sense – a conditional call syntax that solves both issues:
The code of safe_sqrt
now plays differently:
- the
a >= 0
expression is evaluated - the
eve::sqrt[a >= 0]
expression builds a new callable object that will perform the conditional call toeve::sqrt
- this new callable object is then called over
a
- Wherever the condition is true, the
eve::sqrt
function will be applied. - Wherever the condition is false, the value of the first argument of the function will be returned.
The fact the conditional syntax builds a new callable object is interesting because it ensures that any optimization over the conditional computation can be captured and that this new callable can be passed as-if to algorithms without having to worry about changing the number of arguments requested.
!!! Tip If required, the callable object produced by the conditional syntax can be stored into a variable:
Conditional Expressions
If passing a simple logical expression is the most common use-case of the conditional syntax, one may requires more flexibility. To do so, EVE provides various objects to express more interesting conditions.
Mask with alternative
One may want to use the conditional syntax to call a function but instead of returning the first argument if the condition is false, one may want to return an arbitrary value. Another use case could be that you want to use a given condition but you happen to just have computed its complement. You may want to not recompute the proper condition but use the already computed complement.
Both those use cases are handled by the if_ and if_not_ helpers. Those helpers are used to wrap a logical expression so that either an alternative value can be specified or the logical expression will be interpreted as its complement.
Let's modify safe_sqrt
so that, if the argument is not positive, 0 is returned instead.
The output is then:
The same thing can be achieved with if_not_:
The output is then:
Context-sensitive mask
Some algorithms require conditional function calls but use logical expression relative to the element index inside a SIMD value rather than its value. One may want for example to not compute an expression on the first and last element of such SIMD values.
Let's write a function that computes the differences between two vectors in multiple scenarios: not using the first elements and not using the last elements.
The output is then:
!!! Tip The ignore_first and ignore_last conditional takes a number of elements as parameter so they can be applied generically on any size of SIMD value
But what if we want to apply our operation to every element but the first and last one ? Clearly, calling two operations with two different conditional masks is sub-optimal. EVE provides some more conditional expression to express just that like keep_between.
The output is then:
In this case, we used keep_between with ad-hoc indexes, which makes the code a bit too size dependent. One can also use the same conditional but use ignore_first and ignore_last as parameters, keeping the relative aspect of the code.
The output is the same obviously:
Conclusion
Conditional operations on SIMD values is a good way to keep a high level code over some complex computations. EVE provides different levels of abstraction for such operations as well as various helpers to specify how the conditions can be computed based either on values or indexes.