Category Archives: UME::SIMD tutorials

UME::SIMD Tutorial 2: Calculations

Horizontal vs. Vertical vectorization

Once the SIMD vectors are initialized with data, they will be operated upon. When operating on vectors, we need to distinguish two directions of operations: horizontal and vertical. The easiest way to understand the difference is by looking at a following diagram:

horizontal_vertical_vectorization

In the diagram we can distinguish two directions relating to either data orientation or instruction orientation. Vertical vectorization will be a process of operating in an elementwise manner on elements of each vector. For the first ADD operation, the result will be, as if we executed following list of operations:

c0=a0+b0;
c1=a1+b1;
c2=a2+b2;
c3=a3+b3;

Mind that vertical operations can also operate on a single vector, the same way as SQRT operation:

f0=sqrt(e0);
f1=sqrt(e1);
f2=sqrt(e2);
f3=sqrt(e3);

A horizontal operation, or a reduction , is one involving applying an operator between elements of a vector. From the example above this would be HADD operation, which would have the same meaning as:

x=f0+f1+f2+f3;

Another way of remembering the distinction between horizontal and vertical operations is as follows: vertical operation always returns a vector, while horizontal always returns a scalar. By convention names of hoirzontal operations are prefixed with H- letter, such as in HADD.

Invoking operations in UME::SIMD

When operating on vectors simply use overloaded operators, as if you were working with scalars:

UME::SIMD::SIMDVec<float, 4> a, b, c, d, e, f;
...
c = a + b;
e = c * d;

For additional operations, the ones that don’t have an equivalent C++ operator, you can either use a special namespace UME::SIMD::FUNCTIONS or a Member Function Interface (MFI) invocation convention:

f = UME::SIMD::FUNCTIONS::sqrt(e); // or
f = e.sqrt(); // MFI call

MFI invocation convention is especially useful when using UME::SIMD as a code generation target, as it allows invoking every operation supported by the library in the same way. For regular development I advise using operators and functions as this will result in better code readability.

Invocation of a horizontal operation cannot be done using operator syntax, as the concept of reduction operation is not present in current C++ language. You have to therefor use either FUNCTIONS namespace or MFI:

float x = UME::SIMD::FUNCTIONS::hadd(f); // or
float x = e.hadd(); // MFI call

Which syntax convention to use is up to you.

UME::SIMD Tutorial 1: Vector declaration and basic initialization

The programming model used in UME::SIMD is very simple. Instead of using scalar variables, use vector variables. A simple vector declaration can look like:

UME::SIMD::SIMDVec<float, 8> x_vec;

In the above declaration two template parameters have to be passed: number of elements packed in the vector (8) and the fundamental type used to represent each element (float). The fundamental types supported are:

  • unsigned integer: uint8_t (8b), uint16_t (16b), uint32_t (32b) and uint64_t (64b);
  • signed integer: int8_t (8b), int16_t (16b), int32_t (32b) and int64_t (64b);
  • floating point: float(32b) and double (64b).

For the vector length two rules apply:

  • vector length is power of 2, starting with ‘1’,
  • maximum size of a vector is not higher than 1024b.

But what is the reason for these rules? Both limitations are comming from hardware constraints. On hardware level it only makes sense to have registers of length being power of 2, as having the arbitrary vector sizes would require additional die surface to be used. At the same time, the hardware limit is being put on number of bits, rather than on number of elements: vector of 32 64-bit elements would occupy 2048 bit registers, while  a vector of 32 8-bit elements would only use 256 bit registers. Unfortunatelly this means that in the current model, we can operate on up to 128-element uint8_t vectors, but only on 16-element uint64_t vectors.

Once a vector is declared it can be used in a similar way as any fundamental type… with some exceptions. First problem is: how to put actual data in this vector type?

Initialization from scalars

All vector elements can be initialized with the same value already contained in  a scalar variable or constant. To initialize the vector with scalar (or a constant) it is possible to simply write something like:

UME::SIMD::SIMDVec<float, 8> x_vec1(3.14f);
UME::SIMD::SIMDVec<float, 8> x_vec2=3.14f;

float y=2.71f;

UME::SIMD::SIMDVec<float, 8> y_vec1(y);
UME::SIMD::SIMDVec<float, 8> y_vec2=y;

If the initialization has to take place after the vector declaration, it is enough to use the assignment operator (‘=’):

UME::SIMD::SIMDVec<float, 8> x_vec1, x_vec2;
x_vec1=3.14f;
float x=2.71f;
x_vec2=x;

The broadcast initialization is not always feasible as we might want to have different initial values in every vector cell. You can initialize a vector using multiple scalar variables in a following way:

float x1=1.0f, x2=2.0f, x3=3.0f, x4=4.0f;
UME::SIMD::SIMDVec<float, 4> x_vec(x1, x2, x3, x4);

Initialization from memory

In many situations, we don’t want to simply propagate a single value to a vector register. If the initialization data is stored in an array of memory, it is possible to load the variables into the vector register:

float x[4]={1.0f, 2.0f, 3.0f, 4.0f}
// Load-initialize with 'x':
UME::SIMD::SIMDVec<float, 4> x_vec1(x);
// Load from 'x' after vector declaration
UME::SIMD::SIMDVec<float, 4> x_vec2;
x_vec2.load(x);

Loading is important as it allows use of memory pointers instead of scalar variables. This is an important initialization mode especially for long vectors (imagine initializing SIMDVec using scalars…).

In next tutorial we will look into some basic computations that can be performed using vector primitives.

UME::SIMD Tutorial 0: Installation

The library is provided in a header-only form, which makes its’ installation trivial. We will present installation procedures for both Linux and Windows operating systems. Mind that as there is no perfectly portable build system, we only limit ourselves to most common tools used.

This installation procedure shows only steps required for UME::SIMD installation as a standalone library, but we make some small adjustments so that switching to full UME framework would be possible in the future.

Linux installation procedure

The library in its’ primitive form doesn’t require any build system. The only configuration that has to be made is by passing proper compilation flags to the compiler. The specific flags used will depend on both: hardware, compiler and operating system. We will discuss specific configurations in future tutorials in this series.

This installation procedure should work for most of the Linux based systems and requires a GIT client (https://git-scm.com/).

    1. Navigate to the directory where you want to store downloaded the current version of the library and create a directory for the framework:
$ mkdir ume
$ cd ume 
    1. Clone the repository:
$ git clone https://github.com/edanor/umesimd.git 

 

    1. You might want to checkout a tagged version to keep track of library updates:

 

$ cd umesimd
$ git checkout tags/v0.8.1
$ cd ..
    1. export the library directory
$ pwd
/home/ume
$ export CPLUS_INCLUDE_PATH=$CPLUS_INCLUDE_PATH:/home/ume
    1. Include the library code into your C++ project:

Now you should be able to include the library into your program using following directive:

 #include <umesimd/UMESimd.h>

That’s it!

Windows installation procedure

Windows installation procedure is similar, but requires additional steps in configuring the specific IDE. We will discuss this configuration for MS Visual Studio 2015 only, but similar rules apply for others.

    1. Navigate to the directory where you want to store downloaded the current version of the library and create a directory for the framework:
c:\> mkdir ume
c:\> cd ume 
    1. Clone the repository:
c:\ume> git clone https://github.com/edanor/umesimd.git 

 

    1. You might want to checkout a tagged version to keep track of library updates:

 

c:\ume> cd umesimd
c:\ume\umesimd> git checkout tags/v0.8.1
c:\ume> cd ..
    1. Create a new project (or open an existing one):

[Ctrl+Shift+N] or File->New->Project…

Create_project

    1. Add new source file to the project:

[Ctrl+Shift+A] or Project->Add New Item…

Add_main_file

    1. Fill the main.cpp with:
#include <umesimd/UMESimd.h>

int main()
{
    return 0;
}
    1. Open project properties and configure path to the ‘ume’ directory created before

[Alt+Enter] or Project->Properties

Add_ume_path.png

There you go!