This directory contains the tinysimd library, a header only library. The library is a light, zero-overhead wrapper based on template meta-programming that automatically selects a SIMD intrinsics from the most specialized x86-64 instruction set extension available among SSE2 (limited support), AVX2 and AVX512 (not tested) or SVE for the ARM AArch64 architecture. The library is designed to be easily extended to other architectures.
To use the library one needs to import the tinysimd.hpp header. The type traits routines, needed for templated programming are available in the traits.hpp header. It is highly discouraged to perform IO with vector types. If IO is needed for debugging, one needs to import the io.hpp header.
To enable the vector types in Nektar++ you need to set to ON the desired extension (for
instance NEKTAR_ENABLE_SIMD_AVX2). This will automatically set the appropriate compiler
flags. However notice that currently these are set correctly only for gcc and that you might
need to delete the cached variable CMAKE_CXX_FLAGS before configuring cmake. You can check
that the desired vector extension was compiled properly by running the VecDataUnitTests
which prints out the extension in use.
SVE (for instance NEKTAR_ENABLE_SIMD_SVE) is a vector length agnostic ISA extension.
However, in order to wrap the SVE intrinsic types with c++ classes, we fix the size at compile
time. Therefore you need to set appropriate vector size (NEKTAR_SVE_BITS) according to your
target machine.
Note, the extensions are advanced options and only the options relevant to the compiling machine architecture are made available (see NektarSIMD.cmake for more details).
Vector types are largely used with the same semantic as built-in c++ types.
A simple example: if avx2 is available, then this scalar code
1#include <array> 2std::array<double,4> a = {-1.0, -1.0, -1.0, -1.0}; 3std::array<double,4> b; 4for (int i = 0; i < 4; ++i){ 5 b[i] = abs(a[i]); 6}
is equivalent to this vector computation
1#include <LibUtilities/SimdLib/tinysimd.hpp> 2using vec_t = tinysimd::simd<double>; 3vec_t a = -1.0; 4vec_t b = abs(a);
which the compiler translates to the corresponding intrisics
1#include <immintrin.h> 2__m256d a = -1.0; 3__m256d sign_mask = _mm256_set1_pd(1<<63); 4__m256d b = _mm256_andnot_pd(sign_mask, a);
As an example of a method with a complex body with calls to multiple kernels refer to RoeSolverSIMD.cpp.
Usage with matrix free operators: the usage of the tineysimd library in the matrix free operators differs from the above due to the interleaving of n elements degree of freedoms (where n is the vector width) in a contiguous chunk of memory. You can refer to [55] for more details.
General optimization guidelines: a key factor to improve performance on modern architectures is to limit as much as possible data transfer from DRAM to cache
use local temporary variables to store intermediate values
do not call Vmath functions more than once, make a loop over the points instead
if you do call a Vmath function, call the VmathArray version