Utiliser des types numériques accélérés par SIMD
SIMD (Single Instruction Multiple Data) offre la prise en charge matérielle nécessaire pour réaliser une opération sur plusieurs données, en parallèle, avec une seule instruction. .NET inclut un ensemble de types accélérés par SIMD sous l’espace de noms System.Numerics. Les opérations SIMD peuvent être parallélisées au niveau matériel. Cela augmente le débit des calculs vectorisés, couramment utilisés dans les applications mathématiques, scientifiques et graphiques.
Types accélérés par SIMD dans .NET
.NET inclut les types accélérés par SIMD suivants :
Les types Vector2, Vector3 et Vector4, qui représentent des vecteurs à 2, 3 et 4 valeurs Single.
Deux types de matrices : Matrix3x2, qui représente une matrice 3 x 2, et Matrix4x4, qui représente une matrice 4 x 4 de valeurs Single.
Le type Plane, qui représente un plan dans un espace à trois dimensions utilisant des valeurs Single.
Le type Quaternion, qui représente un vecteur utilisé pour encoder des rotations physiques en trois dimensions avec des valeurs Single.
Le type Vector<T>, qui représente un vecteur d’un type numérique spécifié et fournit un large éventail d’opérateurs bénéficiant d’un support SIMD. Le nombre d’éléments d’une instance Vector<T> est fixe pour la durée de vie d’une application, mais la valeur Vector<T>.Count correspondante dépend du processeur de la machine qui exécute le code.
Notes
Le type Vector<T> n’est pas inclus dans .NET Framework. Vous devez installer le package NuGet System.Numerics.Vectors pour accéder à ce type.
Les types accélérés par SIMD sont implémentés pour pouvoir être utilisés avec du matériel non accéléré par SIMD ou des compilateurs JIT. Pour tirer parti des instructions SIMD, vos applications 64 bits doivent être exécutées par le runtime qui utilise le compilateur RyuJIT. Un compilateur RyuJIT est inclus dans .NET Core et dans .NET Framework 4.6 (et versions ultérieures). La prise en charge de SIMD est assurée uniquement quand des processeurs 64 bits sont ciblés.
Comment utiliser SIMD ?
Avant d’exécuter des algorithmes SIMD personnalisés, vous pouvez vérifier si la machine hôte prend en charge SIMD à l’aide de Vector.IsHardwareAccelerated, qui retourne un Boolean. Cela ne garantit pas que l’accélération SIMD est activée pour un type spécifique, mais indique qu’elle est prise en charge par certains types.
Vecteurs simples
Les types accélérés par SIMD les plus primitifs dans .NET sont les types Vector2, Vector3 et Vector4, qui représentent des vecteurs avec 2, 3 et 4 valeurs Single. L’exemple ci-dessous utilise Vector2 pour ajouter deux vecteurs.
var v1 = new Vector2(0.1f, 0.2f);
var v2 = new Vector2(1.1f, 2.2f);
var vResult = v1 + v2;
Il est également possible d’utiliser des vecteurs .NET pour calculer d’autres propriétés mathématiques de vecteurs comme Dot product
, Transform
, Clamp
, etc.
var v1 = new Vector2(0.1f, 0.2f);
var v2 = new Vector2(1.1f, 2.2f);
var vResult1 = Vector2.Dot(v1, v2);
var vResult2 = Vector2.Distance(v1, v2);
var vResult3 = Vector2.Clamp(v1, Vector2.Zero, Vector2.One);
Matrice
Matrix3x2, qui représente une matrice 3 x 2, et Matrix4x4, qui représente une matrice 4 x 4, peuvent être utilisés pour les calculs relatifs à des matrices. L’exemple ci-dessous illustre la multiplication d’une matrice par sa matrice transpose correspondante avec SIMD.
var m1 = new Matrix4x4(
1.1f, 1.2f, 1.3f, 1.4f,
2.1f, 2.2f, 3.3f, 4.4f,
3.1f, 3.2f, 3.3f, 3.4f,
4.1f, 4.2f, 4.3f, 4.4f);
var m2 = Matrix4x4.Transpose(m1);
var mResult = Matrix4x4.Multiply(m1, m2);
Vecteur<T>
Le Vector<T> offre la possibilité d’utiliser des vecteurs plus longs. Le nombre d’éléments d’une instance Vector<T> est fixe, mais la valeur Vector<T>.Count correspondante dépend du processeur de la machine qui exécute le code.
L’exemple suivant montre comment calculer la somme de deux tableaux au niveau des éléments avec Vector<T>.
double[] Sum(double[] left, double[] right)
{
if (left is null)
{
throw new ArgumentNullException(nameof(left));
}
if (right is null)
{
throw new ArgumentNullException(nameof(right));
}
if (left.Length != right.Length)
{
throw new ArgumentException($"{nameof(left)} and {nameof(right)} are not the same length");
}
int length = left.Length;
double[] result = new double[length];
// Get the number of elements that can't be processed in the vector
// NOTE: Vector<T>.Count is a JIT time constant and will get optimized accordingly
int remaining = length % Vector<double>.Count;
for (int i = 0; i < length - remaining; i += Vector<double>.Count)
{
var v1 = new Vector<double>(left, i);
var v2 = new Vector<double>(right, i);
(v1 + v2).CopyTo(result, i);
}
for (int i = length - remaining; i < length; i++)
{
result[i] = left[i] + right[i];
}
return result;
}
Notes
SIMD est plus susceptible de supprimer un goulot d’étranglement et d’exposer le suivant (débit de mémoire, par exemple). L’avantage de l’utilisation de SIMD en termes de performances varie généralement selon le scénario. Dans certains cas, le code non SIMD équivalent (plus simple) est même plus performant.