The SIMD library for humans. Faster allows convenient application of explicit SIMD to existing code. It allows you to write explicit SIMD code once and compile it for any target, regardless of architecture, SIMD capability, or age.

SIMD iterators are formed using simd_iter , simd_iter_mut , and into_simd_iter , which return types which allow the usage of the simd_map and simd_reduce functions. These functions automatically pack your iterator's data into SIMD vectors and allow you to transparently operate on them in a closure.

Once your data is packed into a SIMD vector, you may perform many common SIMD operations on it. These operations have names and behavior independent of any vendor-specific ISA, and have non-SIMD polyfills for machines which cannot perform these operations in a single cycle. See the intrin module for all available operations.

Faster is currently capable of mapping and reductive operations in SIMD.

The simplest example of a computation with faster is a single map operation.

extern crate faster ; use faster :: * ; let lots_of_10s = ( & [ - 10i8 ; 3000 ][..]). simd_iter () . simd_map ( | v | v . abs ()) . scalar_collect (); assert_eq ! ( lots_of_10s , vec ! [ 10u8 ; 3000 ]);

In this example, a vector of type i8s is passed into the closure. The exact type of i8s is dependent on compilation target, but it will always implement the same operations. Because taking the absolute value of a vector converts it to u8s , the closure will return u8s .

scalar_collect takes the iterator of u8s and converts it into a Vec<u8> .

Faster can perform reductive operations with similar power to mapping operations:

extern crate faster ; use faster :: * ; let two_hundred = ( & [ 2.0f32 ; 100 ][..]). simd_iter () . simd_reduce ( f32s :: splat ( 0.0 ), f32s :: splat ( 0.0 ), | acc , v | * acc + * v ) . sum (); assert_eq ! ( two_hundred , 200.0f32 );

This example sums every number in the collection. The first parameter to simd_reduce is the default value of the accumulator, just like any other reduction. The second value is used if the collection being reduced over doesn't fit evenly into your system's vectors - it is the default value of the last vector, and each element of the vector is used only if it isn't filled by an element of the collection. Typically, a value of 0 or 1 is a suitable default.

Minding portability is very important when performing reductive operations. See below for some tips on keeping your code portable across all architectures.

While faster does most of the work ensuring your code stays portable across platforms, a user of this library must still understand that it is very possible to write non-portable algorithms using this library. Anything which relies on vector width, anything which is impure, and anything which uses constants in reductive operations is inherently nonportable. Some examples below:

extern crate faster ; extern crate rand ; use faster :: * ; use rand ::{ thread_rng , Rng }; let impure = ( & [ 1i8 ; 3000 ][..]). simd_iter () . simd_map ( | v | { if thread_rng (). gen () { v + i8s :: splat ( 1 ) } else { v } }) . scalar_collect ();

extern crate faster ; use faster :: * ; let length_dependent = ( & [ 0i8 ; 10 ][..]). simd_iter () . simd_reduce ( i8s :: splat ( 0 ), i8s :: splat ( 0 ), | acc , v | * acc + * v + i8s :: splat ( 1 )). sum ();

As a precaution, it is best practice to keep all functions pure, and only operate on SIMD vectors in your SIMD-enabled closures unless you know exactly what is happening under the hood. It's also important to remember that these problems will crop up even if you only support x86; the width difference between AVX and SSE is the primary source of these issues!