【问题标题】:System.Numerics.Vector<T> Initialization Performance on .NET FrameworkSystem.Numerics.Vector<T> .NET Framework 上的初始化性能
【发布时间】:2021-02-20 01:00:13
【问题描述】:

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 将源数组转换为向量数组 [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

【问题讨论】:

  • github.com/dotnet/corefxlab/issues/2581 ".NET Framework 运行时/JIT 不知道跨度",所以你不应该期望 MemoryMarshal 产生更好的结果。
  • 谢谢,这澄清了结果。因此,span 代码回退到一个通用的、可移植的实现,这种实现速度很慢,并且无法实现相同代码在 .NET Core 上实现的效果:将数组重新解释为向量数组,而无需进行复制。是否有任何解决方案(或许是不同的技巧)可用于提高 .NET Framework 的性能?

标签: c# .net .net-core simd system.numerics


【解决方案1】:

据我所知,在 .NET Framework 4.6 或 4.7 中加载向量的唯一有效方法(大概这将在 5.0 中全部更改)是使用不安全代码,例如使用 Unsafe.Read&lt;Vector&lt;float&gt;&gt;(或其未对齐的变体,如果适用):

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

这使用您可以通过 NuGet 获得的 System.Runtime.CompilerServices.Unsafe 包,但也可以不使用它。

【讨论】:

  • 这是一个很好的解决方案。它在 .NET Framework 上提供与 .NET Core 几乎相同的性能(至少在问题的基准上)。 Naive Vector 初始化都需要 52µs。 “Span”方法在 .NET Core 上运行时间为 31µs,而 Unsafe.Read 方式在 .NET Framework 上运行时间为 34µs。
猜你喜欢
  • 1970-01-01
  • 2022-09-28
  • 2016-06-01
  • 2019-09-19
  • 1970-01-01
  • 1970-01-01
  • 2021-10-06
  • 2016-04-28
  • 2015-08-10
相关资源
最近更新 更多