System.Numerics.Vector <T> Initialisierungsleistung unter .NET Framework
System.Numerics.Vector bietet SIMD-Unterstützung für .NET Core und .NET Framework. Es funktioniert unter .NET Framework 4.6+ und .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];
}
}
Leider kann die Initialisierung des Vektors der begrenzende Schritt sein. Um dies zu umgehen, empfehlen mehrere Quellen die Verwendung von MemoryMarshal, um das Quellarray in ein Array von Vektoren umzuwandeln [1] [2]. Zum Beispiel:
// 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];
}
Dies führt zu einer dramatischen Leistungsverbesserung unter .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 |
Leider hat diese Art der Initialisierung des Vektors in .NET Framework den gegenteiligen Effekt. Es führt tatsächlich zu einer schlechteren Leistung:
| 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 |
Gibt es eine Möglichkeit, die Initialisierung von Vector unter .NET Framework zu optimieren und eine ähnliche Leistung wie .NET Core zu erzielen? Mit dieser Beispielanwendung wurden Messungen durchgeführt [1].
[1] https://github.com/CBGonzalez/SIMDPerformance
[2] https://stackoverflow.com/a/62702334/430935
Antworten
Soweit ich weiß, ist die einzige effiziente Möglichkeit, einen Vektor in .NET Framework 4.6 oder 4.7 zu laden (vermutlich ändert sich dies alles in 5.0), unsicherer Code, beispielsweise mit Unsafe.Read<Vector<float>>(oder seiner nicht ausgerichteten Variante, falls zutreffend):
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];
}
}
Dies verwendet das System.Runtime.CompilerServices.UnsafePaket, das Sie über NuGet erhalten können, aber es könnte auch ohne dies erfolgen.