We already discussed two steps of computations using UME::SIMD: initializing vectors and performing calculations. But what can we do with vectors once we finished our calculations? Unfortunatelly, most of the standard functions and existing libraries don’t use the UME::SIMD types. What is necessary is to somehow get back to scalar world.
One method of doing so, presented in previous tutorial was called horizontal reduction. Since horizontal reductions return a scalar type, it is possible to use returned scalars as inputs to any already defined function.
#include <umesimd/UMESimd.h> float foo(float x) { ... // Do something with x } ... int main() { UME::SIMD::SIMDVec<float, 4> a, b, c; ... a=b*c; float t0=a.hmul(); // Horizontal multiplication foo(t0); return 0; }
Whil extremaly useful in certain situations, horizontal reductions are not always a proper solution. Very often we want to perform only vertical operations, and store the results into some memory array. Here is how to do it:
#include <umesimd/UMESimd.h> float foo(float *x) { ... // Do something with an array of x } ... int main() { UME::SIMD::SIMDVec<float, 4> a, b, c; float temp[4]; ... a=b*c; // STORE the values from vector // in consecutive fields of 'temp' a.store(temp); // pass 'temp' to function foo(temp); return 0; }
In the most often scenario, the workflow with SIMD types will be as follows:
- LOAD values from memory locations to UME::SIMD vectors
- perform calculations using UME::SIMD vectors
- STORE values from UME::SIMD vectors to memory locations.
Remember that storing values from a vector doesn’t destroy the values within the vector, so you can still use the vector for some other calculations.
PERFORMANCE HINT: once values are loaded to a vector, perform as many calculations as possible before storing the results back. LOAD/STORE operations are equivalent to moving data between memory and registers, and can introduce significant latency into your computational kernels.
In C++ the load and store operations are hidden from the programmer under the array indexing operator[]. Most of modern compilers make some additional deductions hiding the load/store (MOV in x86) operations when it is possible to. Since the compiler doesn’t have any knowledge about UME::SIMD types, the burden of deciding when these operations should happen rests on you, the User.