为什么使用 `Span<T>` 比普通数组更昂贵?

PMF*_*PMF 5 c# optimization performance dynamic-memory-allocation

我有一个非常复杂的数学算法,它已经在我的代码中存在多年了。今天,我做了一些高级性能测试,发现该算法占用了大量 CPU 时间。然后我看到(由于其年龄)该算法使用了double[] array = new double[6]几次,然后将数组传递给子函数。我认为使用Span<double>stackalloc double应该会稍微提高性能,因为它减少了 GC 负载并可能减少了一些范围检查。

令我非常惊讶的是,事实恰恰相反,这一变化导致整体测试时间从 23 秒提高到 28 秒。我编写了以下测试用例来展示该行为(实际代码要复杂得多)

        // This one must be quite big to see the timing difference for these simple test cases
        private const int MeasuremenLoopCount = 1000000;
        private const int ArraySize = 10;

        [Fact]
        public void UseArray()
        {
            for (int i = 0; i < MeasuremenLoopCount; i++)
            {
                double[] array = new double[ArraySize];
                array[0] = 2;

                CalcOnArray(array);
            }
        }

        private void CalcOnArray(double[] array)
        {
            for (int i = 1; i < array.Length; i++)
            {
                array[i] = array[i - 1] * 2;
            }
        }

        [Fact]
        public void UseSpanOnArray()
        {
            for (int i = 0; i < MeasuremenLoopCount; i++)
            {
                Span<double> array = new double[ArraySize];
                array[0] = 2;

                CalcOnSpan(array);
            }
        }

        private void CalcOnSpan(Span<double> array)
        {
            for (int i = 1; i < array.Length; i++)
            {
                array[i] = array[i - 1] * 2;
            }
        }

        [Fact]
        public void UseSpanOnStack()
        {
            for (int i = 0; i < MeasuremenLoopCount; i++)
            {
                StackAllocArray();
            }
        }

        // This method only exists to avoid the "use of stackalloc in a loop" error
        private void StackAllocArray()
        {
            Span<double> array = stackalloc double[ArraySize];
            array[0] = 2;

            CalcOnSpan(array);
        }
Run Code Online (Sandbox Code Playgroud)

在我的系统上,

  • UseArray运行时间为 46 毫秒,
  • UseSpanOnArray运行时间为 57 毫秒
  • 运行UseSpanOnStack时间为 55 毫秒。

后两者之间的(小)差异可能是使用堆栈相对于堆的改进,但为什么UseArray明显快于UseSpanOnArray

以下是探查器运行的详细信息,测试用例之间的关系相同,但没有太多额外的见解: 分析器结果