Vectorization can enhance and speed up your C# program during and after development, but there are some things you need to know before you begin. We will begin with a short explanation of what vectorization is. Then the prerequisites to enable hardware-assisted vectorization are presented since the new JIT compiler (named RyuJIT) is not part of any .NET or Visual Studio release candidates as of this writing. With this information, you can enable the necessary bits that implement hardware-assisted vectorization. Next, the .NET vectorization classes are presented and described so that you can understand the tools that you have to work with when implementing vectorization in C#. Finally, several examples are given for how you can use vectorization in C#.
What Is Vectorization?
The process of vectorization applies an operation to an entire array of data. For instance, adding 3 to every element of an array used to require a loop in which each array element was updated. With vectorization, it is a matter of using special constructs to perform the operation on an entire data set without the need of a loop.
There are two advantages of vectorization. The first and most obvious is the performance advantage that vectorization delivers. Any processor capable of single instruction, multiple data (SIMD) operations can carry out hardware-assisted vectorization. The gains can be dramatic, and in some cases providing up to a four-fold boost in performance. The second reason to use vectorization is that it employs a software engineering pattern that functional languages such as Haskell, Erlang, and F# have been using for years. When using these patterns that imitate functions such as map from Haskell, the programming paradigm shifts to what many believe demonstrates a clearer meaning.
Configuring for Hardware-Enabled Vectorization
Since the .NET JIT compiler is not part of the current deliverables, a set of prerelease bits must be used to provide hardware-assisted vectorization to the .NET runtime, and thus your C# programs. In addition, there are several configuration steps that must be taken in order for RyuJIT to be used instead of the standard JIT compiler. First, you must download and install the current build of RyuJIT. The current link to Community Technology Preview (CTP) 5 can be found at http://aka.ms/RyuJIT. Once you have downloaded and installed RyuJIT, it must be enabled with the following commands that modify the registry.
reg add HKCU\SOFTWARE\Microsoft\.NETFramework /v AltJit /t REG_SZ /d "*" /f /reg:64 > NUL
reg add HKCU\SOFTWARE\Microsoft\.NETFramework /v FeatureSIMD /t REG_DWORD /d 1 /f /reg:64 > NUL
Once you are done with your testing, it is advisable to return your registry to the pre-RyuJIT settings for production work with the following commands.
reg delete HKCU\SOFTWARE\Microsoft\.NETFramework /v AltJit /f /reg:64 > NUL
reg delete HKCU\SOFTWARE\Microsoft\.NETFramework /v FeatureSIMD /f /reg:64 > NUL
The C# hardware-assisted vectorization only works in WPF applications right now. The following code must be added to get everything working. This will not be necessary for the final release version.
static Vector vTest;
vTest = Vector.One;
In Visual Studio from the Tools menu, select Options/Debugging/General and deselect Suppress JIT optimization on module load. You must also add System.Numerics to the project references.
The last thing you must do to use RyuJIT is to add the following command to the Package Manager Console.
Install-Package Microsoft.Bcl.Simd -Version 1.1.5-beta –Pre
From this point, you should be good to go to utilize hardware-enabled vectorization in your C# programs.
.NET Vector Classes
There are several .NET vector classes that can be used. Their differences mainly revolve around the dimension of the array that they encapsulate. For instance, for a single dimension array, you should use the Vector class. For a two dimension array, you should use a Vector2f class. Another important difference between the Vector class and the others such as Vector2f, Vector3f, and Vector4f is that Vector2f, Vector3f, and Vector4f all encapsulate floats while Vector can encapsulate byte, int, float, and double. For the remainder of this article we will only consider the Vector class, and in later articles we will address the other C# vector classes.
Using the Vector Class
For starters, we need to create a Vector class. For this exercise we will assume that we have an integer array with values that are important to our program. The following line creates a Vector object that encapsulates the values that are contained in the integer array.
// array contains 5, 7, 9, 12, 11
Vector<int> vMyValues = new Vector<int>( nValues );
Now we are ready for some of the vector magic. Let’s add 3 to the entire data set with a single call as follows. With SIMD enabled, this will execute in a fraction of the time it takes when SIMD is not enabled.
vMyValues += new Vector<int>(3);
// array now contains 8, 10, 12, 15, 14
You can just as easily and just as quickly add the values in a second Vector to the values in the vMyValue object above with the following code.
// vMyOtherValues contains 4, 3, 2, -1, -4
// and vMyValues now contains 8, 10, 12, 15, 14
vMyValues += vMyOtherValues;
// vMyValues now contains 12, 13, 14, 14, 10
There are many operations that you can perform on vectors including add, subtract, multiply, divide, calculate a cross product, and negate. In addition, VectorMath provides a wide range of comparison operators that help compare two vectors. Using vectors can speed up your program during development, and upon deployment once the new JIT compiler is officially released. And the bonus is that you can brag to your friends who love functional programming that you use some of the same software engineering patterns that they are so proud of.