.NET Framework의 System.Numerics.Vector <T> 초기화 성능

Nov 07 2020

System.Numerics.Vector는 .NET Core 및 .NET Framework에 SIMD 지원을 제공합니다. .NET Framework 4.6 이상 및 .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];
    }
}

불행히도 Vector의 초기화는 제한 단계가 될 수 있습니다. 이 문제를 해결하기 위해 여러 소스에서 MemoryMarshal을 사용하여 소스 배열을 Vectors [1] [2] 배열로 변환 할 것을 권장합니다. 예를 들면 :

// 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];
}

이는 .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 |

안타깝게도 .NET Framework 에서는 이러한 벡터 초기화 방법이 반대 효과를냅니다. 실제로 성능이 저하됩니다.

|                 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 |

.NET Framework에서 Vector의 초기화를 최적화하고 .NET Core와 유사한 성능을 얻을 수있는 방법이 있습니까? 이 샘플 애플리케이션을 사용하여 측정을 수행했습니다 [1].

[1] https://github.com/CBGonzalez/SIMDPerformance

[2] https://stackoverflow.com/a/62702334/430935

답변

2 harold Nov 08 2020 at 10:18

내가 아는 한, .NET Framework 4.6 또는 4.7에서 벡터를로드하는 유일한 효율적인 방법은 안전하지 않은 코드를 사용하는 것입니다. 예를 들어 다음을 사용하는 것입니다 Unsafe.Read<Vector<float>>.

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];
    }
}

이것은 System.Runtime.CompilerServices.UnsafeNuGet을 통해 얻을 수 있는 패키지를 사용 하지만 그것 없이도 할 수 있습니다.