System.Numerics.Vector <T> Performances d'initialisation sur .NET Framework
System.Numerics.Vector apporte le support SIMD à .NET Core et .NET Framework. Il fonctionne sur .NET Framework 4.6+ et .NET Core.
// Baseline
public void SimpleSumArray()
{
for (int i = 0; i < left.Length; i++)
results[i] = left[i] + right[i];
}
// Using Vector<T> for SIMD support
public void SimpleSumVectors()
{
int ceiling = left.Length / floatSlots * floatSlots;
for (int i = 0; i < ceiling; i += floatSlots)
{
Vector<float> v1 = new Vector<float>(left, i);
Vector<float> v2 = new Vector<float>(right, i);
(v1 + v2).CopyTo(results, i);
}
for (int i = ceiling; i < left.Length; i++)
{
results[i] = left[i] + right[i];
}
}
Malheureusement, l'initialisation du vecteur peut être l'étape limitante. Pour contourner ce problème, plusieurs sources recommandent d'utiliser MemoryMarshal pour transformer le tableau source en un tableau de vecteurs [1] [2]. Par exemple:
// Improving Vector<T> Initialization Performance
public void SimpleSumVectorsNoCopy()
{
int numVectors = left.Length / floatSlots;
int ceiling = numVectors * floatSlots;
// leftMemory is simply a ReadOnlyMemory<float> referring to the "left" array
ReadOnlySpan<Vector<float>> leftVecArray = MemoryMarshal.Cast<float, Vector<float>>(leftMemory.Span);
ReadOnlySpan<Vector<float>> rightVecArray = MemoryMarshal.Cast<float, Vector<float>>(rightMemory.Span);
Span<Vector<float>> resultsVecArray = MemoryMarshal.Cast<float, Vector<float>>(resultsMemory.Span);
for (int i = 0; i < numVectors; i++)
resultsVecArray[i] = leftVecArray[i] + rightVecArray[i];
}
Cela apporte une amélioration spectaculaire des performances lors de l'exécution sur .NET Core :
| Method | Mean | Error | StdDev |
|----------------------- |----------:|----------:|----------:|
| SimpleSumArray | 165.90 us | 0.1393 us | 0.1303 us |
| SimpleSumVectors | 53.69 us | 0.0473 us | 0.0443 us |
| SimpleSumVectorsNoCopy | 31.65 us | 0.1242 us | 0.1162 us |
Malheureusement, sur .NET Framework , cette façon d'initialiser le vecteur a l'effet inverse. Cela conduit en fait à de pires performances:
| Method | Mean | Error | StdDev |
|----------------------- |----------:|---------:|---------:|
| SimpleSumArray | 152.92 us | 0.128 us | 0.114 us |
| SimpleSumVectors | 52.35 us | 0.041 us | 0.038 us |
| SimpleSumVectorsNoCopy | 77.50 us | 0.089 us | 0.084 us |
Existe-t-il un moyen d'optimiser l'initialisation de Vector sur .NET Framework et d'obtenir des performances similaires à .NET Core? Des mesures ont été effectuées à l'aide de cet exemple d'application [1].
[1] https://github.com/CBGonzalez/SIMDPerformance
[2] https://stackoverflow.com/a/62702334/430935
Réponses
Pour autant que je sache, le seul moyen efficace de charger un vecteur dans .NET Framework 4.6 ou 4.7 (tout changera probablement dans 5.0) est d'utiliser du code non sécurisé, par exemple en utilisant Unsafe.Read<Vector<float>>(ou sa variante non calibrée le cas échéant):
public unsafe void SimpleSumVectors()
{
int ceiling = left.Length / floatSlots * floatSlots;
fixed (float* leftp = left, rightp = right, resultsp = results)
{
for (int i = 0; i < ceiling; i += floatSlots)
{
Unsafe.Write(resultsp + i,
Unsafe.Read<Vector<float>>(leftp + i) + Unsafe.Read<Vector<float>>(rightp + i));
}
}
for (int i = ceiling; i < left.Length; i++)
{
results[i] = left[i] + right[i];
}
}
Cela utilise le System.Runtime.CompilerServices.Unsafepackage que vous pouvez obtenir via NuGet, mais cela pourrait également être fait sans cela.