.NET9 实现斐波那契数列(FibonacciSequence)性能测试

.NET 平台上实现 斐波那契数列 并使用 BenchmarkDotNet 进行性能测试,是评估不同算法实现方式性能表现的一种高效且标准化的方法。通过该方式,可以对比递归、迭代、记忆化递归以及结合高性能优化技术(如 Span<T>Memory<T>ArrayPool<T>)的多种实现,在不同输入规模下的执行时间、内存分配和垃圾回收行为。

整个过程包括:

  1. 选择合适的斐波那契实现方式

    • 递归实现:直观但效率低下,尤其在大数值时存在指数级时间复杂度。
    • 迭代实现:性能最优,适用于大多数生产环境。
    • 记忆化递归:通过缓存减少重复计算,提升递归效率。
    • 结合 ArrayPool 的记忆化递归 :避免频繁内存分配,降低 GC 压力。
    • 使用 Span<T>Memory<T> 的实现:进一步优化内存访问效率,支持更灵活的异步或池化操作。
  2. 构建基准测试类

    使用 BenchmarkDotNet 提供的 [Benchmark] 特性对每个实现方法进行标注,并通过 [Params] 指定多个输入值(如 N = 10, 30, 40),以模拟不同场景下的运行情况。

  3. 启用诊断功能

    在基准测试类上添加 [MemoryDiagnoser] 等特性,启用内存统计功能,获取每次调用的堆内存分配信息,帮助识别潜在的性能瓶颈。

  4. 运行基准测试

    使用 BenchmarkRunner.Run<T>() 启动测试,生成结构化的性能报告,包含 平均耗时(Mean)、误差范围(Error)、标准差(StdDev)、Gen0/Gen1 垃圾回收次数及内存分配量 等关键指标。

  5. 分析结果并优化实现

    根据测试报告数据,判断哪种实现方式在特定场景下具有最佳性能表现。例如,迭代法通常最快且无内存分配,而结合 ArrayPool<T> 的记忆化递归则在保留递归风格的同时大幅提升了性能。

最终,这一流程不仅验证了各类斐波那契实现的实际性能差异,也为实际项目中选择合适的算法提供了可靠的数据支撑。

项目准备

  • 项目环境
xml 复制代码
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net9.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
    <PublishAot>true</PublishAot>
    <InvariantGlobalization>true</InvariantGlobalization>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Datadog.Trace.BenchmarkDotNet" Version="2.61.0" />
  </ItemGroup>

</Project>
  • 斐波那契数列实现
csharp 复制代码
// =============================
// FibonacciSequence 斐波那契数列实现
// =============================

using System.Buffers;

namespace FibonacciSequenceTest;

internal class FibonacciSequence
{
    // 递归实现(效率低)
    public static long Recursive(int n)
    {
        if (n <= 1) return n;
        return Recursive(n - 1) + Recursive(n - 2);
    }

    // 迭代实现(高效)
    public static long Iterative(int n)
    {
        if (n <= 1) return n;

        long a = 0, b = 1;
        for (int i = 2; i <= n; i++)
        {
            long temp = a + b;
            a = b;
            b = temp;
        }
        return b;
    }

    // 带缓存的递归实现(记忆化)
    public static long Memoized(int n)
    {
        var memo = new long[n + 1];
        return FibMemo(n, memo);
    }

    private static long FibMemo(int n, long[] memo)
    {
        if (n <= 1) return n;
        if (memo[n] != 0) return memo[n];

        memo[n] = FibMemo(n - 1, memo) + FibMemo(n - 2, memo);
        return memo[n];
    }

    // 使用 ArrayPool 优化的记忆化递归实现
    public static long MemoizedWithPooling(int n)
    {
        // 从 ArrayPool 获取足够大小的缓存数组
        int length = n + 1;
        var memo = ArrayPool<long>.Shared.Rent(length);
        try
        {
            return FibMemo(n, memo);
        }
        finally
        {
            // 用完后归还数组,避免内存泄漏
            if (memo != null)
                ArrayPool<long>.Shared.Return(memo);
        }
    }

    // 使用 ArrayPool + Span 优化的记忆化递归实现
    public static long MemoizedWithSpan(int n)
    {
        int length = n + 1;
        var memo = ArrayPool<long>.Shared.Rent(length);
        try
        {
            return FibMemoWithSpan(n, memo.AsSpan());
        }
        finally
        {
            if (memo != null)
                ArrayPool<long>.Shared.Return(memo);
        }
    }

    private static long FibMemoWithSpan(int n, Span<long> memo)
    {
        if (n <= 1) return n;
        if (memo[n] != 0) return memo[n];

        memo[n] = FibMemoWithSpan(n - 1, memo) + FibMemoWithSpan(n - 2, memo);
        return memo[n];
    }

    // 使用 ArrayPool + Memory 优化的记忆化递归实现
    public static long MemoizedWithMemory(int n)
    {
        int length = n + 1;
        var memo = ArrayPool<long>.Shared.Rent(length);
        try
        {
            return FibMemoWithMemory(n, memo.AsMemory());
        }
        finally
        {
            if (memo != null)
                ArrayPool<long>.Shared.Return(memo);
        }
    }

    private static long FibMemoWithMemory(int n, Memory<long> memo)
    {
        if (n <= 1) return n;

        // 将 Memory<long> 转换为 Span<long>,以支持索引操作
        Span<long> span = memo.Span;

        if (span[n] != 0) return span[n];

        span[n] = FibMemoWithMemory(n - 1, memo) + FibMemoWithMemory(n - 2, memo);
        return span[n];
    }
}
  • FibonacciSequence 测试类
csharp 复制代码
// =============================
// FibonacciSequence 测试类
// =============================

using BenchmarkDotNet.Attributes;
using Datadog.Trace.BenchmarkDotNet;

namespace FibonacciSequenceTest;

[DatadogDiagnoser]
[MemoryDiagnoser]
public class FibonacciBenchmark
{
    [Params(10, 30, 40)] // 测试不同的 n 值
    public int N { get; set; }

    [Benchmark]
    public long RecursiveFibonacci() => FibonacciSequence.Recursive(N);

    [Benchmark]
    public long IterativeFibonacci() => FibonacciSequence.Iterative(N);

    [Benchmark]
    public long MemoizedFibonacci() => FibonacciSequence.Memoized(N);

    [Benchmark]
    public long MemoizedWithPoolingFibonacci() => FibonacciSequence.MemoizedWithPooling(N);

    [Benchmark]
    public long MemoizedWithSpanFibonacci() => FibonacciSequence.MemoizedWithSpan(N);

    [Benchmark]
    public long MemoizedWithMemoryFibonacci() => FibonacciSequence.MemoizedWithMemory(N);
}
  • 使用基准测试

Program.cs 文件中添加如下代码:

csharp 复制代码
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Running;
using Datadog.Trace.BenchmarkDotNet;

namespace FibonacciSequenceTest;

internal class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("Hello, SortingBenchmark!");
        
        var fibonacciSummary = BenchmarkRunner.Run<FibonacciBenchmark>();
    }
}

启动测试

进入项目,使用 pwsh 执行如下命令:

bash 复制代码
dotnet run -c Release

这段文本是一个使用 BenchmarkDotNet 工具对不同 斐波那契数列(Fibonacci)算法实现 的性能基准测试结果报告。它对比了多种实现方式在不同输入规模 N 下的执行效率、内存分配等指标。


以下是关键内容的通俗解释:

📊 表格结构说明

列名 含义
Method 测试的方法名称(不同的 Fibonacci 实现)
N 输入参数,表示求第 N 个斐波那契数
Mean 平均耗时(单位:纳秒 ns 或 微秒 μs)
Error 置信区间的一半(99.9% 置信度)
StdDev 标准差,衡量运行时间波动
Median 中位数,排除极端值后的时间
Gen0 Gen0 垃圾回收次数(每千次操作)
Allocated 每次操作分配的托管内存大小

🧪 被测试的斐波那契实现方法

方法名 实现方式 是否推荐
RecursiveFibonacci 普通递归(无优化) ❌ 不推荐
IterativeFibonacci 迭代法(最高效) ✅ 强烈推荐
MemoizedFibonacci 使用数组缓存的记忆化递归 ⚠️ 可用但有内存分配
MemoizedWithPoolingFibonacci 使用 ArrayPool<long> 缓存数组优化 ✅ 推荐
MemoizedWithSpanFibonacci 使用 ArrayPool<long> + Span<long> + 缓存 ✅ 推荐
MemoizedWithMemoryFibonacci 使用 ArrayPool<long> + Memory<long> + 缓存 ✅ 推荐

📈 性能对比分析(按 N 分组)

N = 10 时:

方法 平均耗时 内存分配
RecursiveFibonacci 251.435 ns -
IterativeFibonacci 7.234 ns -
MemoizedFibonacci 63.627 ns 112 B
MemoizedWithPoolingFibonacci 18.526 ns -
MemoizedWithSpanFibonacci 21.416 ns -
MemoizedWithMemoryFibonacci 20.367 ns -

📌 结论:

  • 迭代法最快(仅 7ns)
  • 普通递归较慢
  • 使用池化或 Span/ Memory 优化后的记忆化递归显著优于普通递归

N = 30 时:

方法 平均耗时 内存分配
RecursiveFibonacci 3,372,317 ns(3.37ms) -
IterativeFibonacci 26.832 ns -
MemoizedFibonacci 301.255 ns 272 B
MemoizedWithPoolingFibonacci 18.624 ns -
MemoizedWithSpanFibonacci 19.883 ns -
MemoizedWithMemoryFibonacci 24.130 ns -

📌 结论:

  • 普通递归性能急剧下降(指数级增长)
  • 其他优化方法依然保持稳定低耗时
  • 迭代法仍是最快

N = 40 时:

方法 平均耗时 内存分配
RecursiveFibonacci 416,127,408 ns(约 416ms) -
IterativeFibonacci 35.565 ns -
MemoizedFibonacci 436.763 ns 352 B
MemoizedWithPoolingFibonacci 18.548 ns -
MemoizedWithSpanFibonacci 19.698 ns -
MemoizedWithMemoryFibonacci 20.206 ns -

📌 结论:

  • 普通递归已变得不可接受
  • 所有优化版本仍保持微秒级响应
  • 迭代法依旧最优

✅ 总结与建议

特性 推荐实现
最快实现 IterativeFibonacci(迭代法)
最节省内存 MemoizedWithPoolingFibonacci(结合 ArrayPool)
支持异步和长期持有 MemoizedWithMemoryFibonacci
保留递归风格又兼顾性能 MemoizedWithPoolingFibonacciMemoizedWithSpanFibonacci

📝 小结

方法 性能 内存 是否推荐
普通递归 ❌ 极慢
迭代法 ✅ 极快 无分配 ✅ 强烈推荐
记忆化递归 ⚠️ 中等 一般
记忆化 + ArrayPool/Span/Memory ✅ 快 无分配 ✅ 推荐保留递归风格时使用
相关推荐
军训猫猫头12 小时前
1.如何对多个控件进行高效的绑定 C#例子 WPF例子
开发语言·算法·c#·.net
追逐时光者12 小时前
C#/.NET/.NET Core优秀项目和框架2025年6月简报
后端·.net
唐青枫2 天前
C#.NET log4net 详解
c#·.net
ChaITSimpleLove2 天前
使用 Dockerfile 构建基于 .NET9 的跨平台基础镜像
.net·dockerfile·.net aspire·dotnet-sdk·pwsh·docker image·docker buildx
专注VB编程开发20年2 天前
C#,VB.NET从JSON数据里提取数组中的对象节点值
c#·json·.net
界面开发小八哥2 天前
界面组件DevExpress WPF中文教程:Grid - 如何获取节点?
.net·wpf·界面控件·devexpress·ui开发
贾修行2 天前
.NET 8.0 Redis 教程
数据库·redis·.net
今晚打老虎z2 天前
dotnet-env: .NET 开发者的环境变量加载工具
前端·chrome·.net