Skip to content

性能分析

学习如何分析和优化源生成器的性能

📋 文档信息

难度级别: 高级
预计阅读时间: 25 分钟
前置知识:

  • 源生成器基础
  • 性能分析基础
  • C# 性能优化

🎯 学习目标

学完本章节后,你将能够:

  • 测量生成器的执行时间
  • 识别性能瓶颈
  • 应用性能优化策略
  • 使用 BenchmarkDotNet 进行基准测试
  • 实施性能监控

⚡ 性能分析和优化

测量执行时间

csharp
public void Initialize(IncrementalGeneratorInitializationContext context)
{
    var classes = context.SyntaxProvider
        .CreateSyntaxProvider(
            predicate: (node, _) => node is ClassDeclarationSyntax,
            transform: (ctx, _) =>
            {
                var sw = System.Diagnostics.Stopwatch.StartNew();
                var result = ProcessClass(ctx);
                sw.Stop();
                
                if (sw.ElapsedMilliseconds > 100)
                {
                    System.Diagnostics.Debug.WriteLine(
                        $"Slow processing: {sw.ElapsedMilliseconds}ms");
                }
                
                return result;
            });
}

性能分析辅助类

csharp
/// <summary>
/// 性能分析辅助类
/// </summary>
public static class PerformanceAnalyzer
{
    private static readonly Dictionary<string, List<long>> Timings = new();
    private static readonly object Lock = new();
    
    /// <summary>
    /// 测量代码块执行时间
    /// </summary>
    public static IDisposable Measure(string operationName)
    {
        return new PerformanceMeasurement(operationName);
    }
    
    /// <summary>
    /// 获取性能报告
    /// </summary>
    public static string GetReport()
    {
        lock (Lock)
        {
            var sb = new StringBuilder();
            sb.AppendLine("=== Performance Report ===");
            
            foreach (var (operation, times) in Timings.OrderByDescending(x => x.Value.Average()))
            {
                var avg = times.Average();
                var min = times.Min();
                var max = times.Max();
                var count = times.Count;
                
                sb.AppendLine($"{operation}:");
                sb.AppendLine($"  Count: {count}");
                sb.AppendLine($"  Avg: {avg:F2}ms");
                sb.AppendLine($"  Min: {min}ms");
                sb.AppendLine($"  Max: {max}ms");
                sb.AppendLine($"  Total: {times.Sum()}ms");
            }
            
            return sb.ToString();
        }
    }
    
    private class PerformanceMeasurement : IDisposable
    {
        private readonly string _operationName;
        private readonly Stopwatch _stopwatch;
        
        public PerformanceMeasurement(string operationName)
        {
            _operationName = operationName;
            _stopwatch = Stopwatch.StartNew();
        }
        
        public void Dispose()
        {
            _stopwatch.Stop();
            
            lock (Lock)
            {
                if (!Timings.ContainsKey(_operationName))
                {
                    Timings[_operationName] = new List<long>();
                }
                
                Timings[_operationName].Add(_stopwatch.ElapsedMilliseconds);
            }
        }
    }
}

使用性能分析器

csharp
[Generator]
public class PerformanceAwareGenerator : IIncrementalGenerator
{
    public void Initialize(IncrementalGeneratorInitializationContext context)
    {
        var classes = context.SyntaxProvider
            .ForAttributeWithMetadataName(
                "GenerateAttribute",
                predicate: (node, _) =>
                {
                    using (PerformanceAnalyzer.Measure("Predicate"))
                    {
                        return node is ClassDeclarationSyntax;
                    }
                },
                transform: (ctx, _) =>
                {
                    using (PerformanceAnalyzer.Measure("Transform"))
                    {
                        return GetClassInfo(ctx);
                    }
                });
        
        context.RegisterSourceOutput(classes, (spc, classInfo) =>
        {
            using (PerformanceAnalyzer.Measure("CodeGeneration"))
            {
                var code = GenerateCode(classInfo);
                spc.AddSource($"{classInfo.Name}.g.cs", code);
            }
        });
        
        // 输出性能报告
        #if DEBUG
        var report = PerformanceAnalyzer.GetReport();
        File.WriteAllText(
            Path.Combine(Path.GetTempPath(), "generator-performance.txt"),
            report);
        #endif
    }
    
    private ClassInfo GetClassInfo(GeneratorAttributeSyntaxContext context)
    {
        using (PerformanceAnalyzer.Measure("GetClassInfo"))
        {
            var classSymbol = (INamedTypeSymbol)context.TargetSymbol;
            
            using (PerformanceAnalyzer.Measure("GetProperties"))
            {
                var properties = classSymbol.GetMembers()
                    .OfType<IPropertySymbol>()
                    .ToList();
            }
            
            return new ClassInfo(classSymbol.Name);
        }
    }
    
    private string GenerateCode(ClassInfo classInfo)
    {
        using (PerformanceAnalyzer.Measure("GenerateCode"))
        {
            return $"public partial class {classInfo.Name} {{ }}";
        }
    }
    
    private record ClassInfo(string Name);
}

性能报告示例:

=== Performance Report ===
Transform:
  Count: 50
  Avg: 12.34ms
  Min: 5ms
  Max: 45ms
  Total: 617ms
CodeGeneration:
  Count: 50
  Avg: 3.21ms
  Min: 1ms
  Max: 15ms
  Total: 160.5ms
Predicate:
  Count: 200
  Avg: 0.15ms
  Min: 0ms
  Max: 2ms
  Total: 30ms

📊 性能优化检查清单

✅ 最佳实践

  1. 使用增量生成器
csharp
// ✅ 推荐:使用 IIncrementalGenerator
[Generator]
public class MyGenerator : IIncrementalGenerator
{
    public void Initialize(IncrementalGeneratorInitializationContext context)
    {
        // 增量管道...
    }
}
  1. 早期过滤
csharp
// ✅ 推荐:在 predicate 中快速过滤
var classes = context.SyntaxProvider
    .ForAttributeWithMetadataName(
        "GenerateAttribute",
        predicate: (node, _) => 
            node is ClassDeclarationSyntax cls &&
            cls.Modifiers.Any(m => m.IsKind(SyntaxKind.PartialKeyword)),
        transform: (ctx, _) => GetClassInfo(ctx));
  1. 避免重复计算
csharp
// ✅ 推荐:缓存计算结果
private readonly Dictionary<string, ClassInfo> _cache = new();

private ClassInfo GetClassInfo(GeneratorAttributeSyntaxContext context)
{
    var classSymbol = (INamedTypeSymbol)context.TargetSymbol;
    var key = classSymbol.ToDisplayString();
    
    if (_cache.TryGetValue(key, out var cached))
    {
        return cached;
    }
    
    var info = new ClassInfo(classSymbol.Name);
    _cache[key] = info;
    return info;
}
  1. 使用 StringBuilder
csharp
// ✅ 推荐:使用 StringBuilder 构建代码
private string GenerateCode(ClassInfo classInfo)
{
    var sb = new StringBuilder();
    sb.AppendLine($"public partial class {classInfo.Name}");
    sb.AppendLine("{");
    // ...
    sb.AppendLine("}");
    return sb.ToString();
}

❌ 反模式

  1. 在 transform 中进行复杂过滤
csharp
// ❌ 避免:在 transform 中过滤
var classes = context.SyntaxProvider
    .ForAttributeWithMetadataName(
        "GenerateAttribute",
        predicate: (node, _) => true, // 不过滤
        transform: (ctx, _) =>
        {
            var symbol = ctx.TargetSymbol as INamedTypeSymbol;
            if (symbol == null) return null; // 在这里过滤,性能差
            if (!symbol.IsPartialDefinition()) return null;
            return GetClassInfo(ctx);
        });
  1. 使用字符串拼接
csharp
// ❌ 避免:使用字符串拼接
private string GenerateCode(ClassInfo classInfo)
{
    string code = "public partial class " + classInfo.Name + "\n{\n";
    // 性能差...
    return code;
}
  1. 过度使用语义分析
csharp
// ❌ 避免:不必要的语义分析
private bool IsValidClass(SyntaxNode node, SemanticModel model)
{
    // 每次都进行语义分析,性能差
    var symbol = model.GetDeclaredSymbol(node);
    return symbol != null;
}

🔬 使用 BenchmarkDotNet

设置基准测试

csharp
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

[MemoryDiagnoser]
[SimpleJob(warmupCount: 3, targetCount: 5)]
public class GeneratorBenchmarks
{
    private Compilation _compilation;
    private MyGenerator _generator;
    
    [GlobalSetup]
    public void Setup()
    {
        var source = @"
            [Generate]
            public class TestClass
            {
                public string Name { get; set; }
                public int Age { get; set; }
            }
        ";
        
        _compilation = CreateCompilation(source);
        _generator = new MyGenerator();
    }
    
    [Benchmark]
    public void RunGenerator()
    {
        var driver = CSharpGeneratorDriver.Create(_generator);
        driver = driver.RunGenerators(_compilation);
    }
    
    [Benchmark]
    public void RunGeneratorWithLargeInput()
    {
        var largeSource = GenerateLargeSource(1000);
        var compilation = CreateCompilation(largeSource);
        var driver = CSharpGeneratorDriver.Create(_generator);
        driver = driver.RunGenerators(compilation);
    }
    
    private string GenerateLargeSource(int classCount)
    {
        var sb = new StringBuilder();
        for (int i = 0; i < classCount; i++)
        {
            sb.AppendLine($@"
                [Generate]
                public class TestClass{i}
                {{
                    public string Property{i} {{ get; set; }}
                }}
            ");
        }
        return sb.ToString();
    }
}

// 运行基准测试
class Program
{
    static void Main(string[] args)
    {
        var summary = BenchmarkRunner.Run<GeneratorBenchmarks>();
    }
}

基准测试结果示例

|                    Method |     Mean |    Error |   StdDev |  Gen 0 | Allocated |
|-------------------------- |---------:|---------:|---------:|-------:|----------:|
|             RunGenerator |  12.5 ms |  0.24 ms |  0.22 ms | 125.00 |    512 KB |
| RunGeneratorWithLargeInput | 125.3 ms |  2.45 ms |  2.29 ms | 1250.0 |   5120 KB |

📈 性能监控

实时性能监控

csharp
public class PerformanceMonitor
{
    private readonly Dictionary<string, Stopwatch> _timers = new();
    private readonly Dictionary<string, long> _counters = new();
    
    public void StartTimer(string name)
    {
        if (!_timers.ContainsKey(name))
        {
            _timers[name] = new Stopwatch();
        }
        _timers[name].Restart();
    }
    
    public void StopTimer(string name)
    {
        if (_timers.TryGetValue(name, out var timer))
        {
            timer.Stop();
        }
    }
    
    public void IncrementCounter(string name)
    {
        if (!_counters.ContainsKey(name))
        {
            _counters[name] = 0;
        }
        _counters[name]++;
    }
    
    public string GetReport()
    {
        var sb = new StringBuilder();
        sb.AppendLine("Performance Report");
        sb.AppendLine("==================");
        
        sb.AppendLine("\nTimers:");
        foreach (var (name, timer) in _timers)
        {
            sb.AppendLine($"  {name}: {timer.ElapsedMilliseconds}ms");
        }
        
        sb.AppendLine("\nCounters:");
        foreach (var (name, count) in _counters)
        {
            sb.AppendLine($"  {name}: {count}");
        }
        
        return sb.ToString();
    }
}

// 使用
public void Initialize(IncrementalGeneratorInitializationContext context)
{
    var monitor = new PerformanceMonitor();
    
    var classInfos = context.SyntaxProvider
        .ForAttributeWithMetadataName(
            "GenerateAttribute",
            predicate: (node, _) => node is ClassDeclarationSyntax,
            transform: (ctx, _) =>
            {
                monitor.StartTimer("Transform");
                monitor.IncrementCounter("ClassesProcessed");
                
                var result = GetClassInfo(ctx);
                
                monitor.StopTimer("Transform");
                return result;
            });
    
    context.RegisterSourceOutput(classInfos, (spc, classInfo) =>
    {
        monitor.StartTimer("CodeGeneration");
        
        var code = GenerateCode(classInfo);
        spc.AddSource($"{classInfo.Name}.g.cs", code);
        
        monitor.StopTimer("CodeGeneration");
        
        // 报告性能数据
        spc.ReportDiagnostic(Diagnostic.Create(
            new DiagnosticDescriptor(
                "PERF001",
                "Performance Report",
                monitor.GetReport(),
                "Performance",
                DiagnosticSeverity.Info,
                true),
            Location.None));
    });
}

🔗 相关资源

💡 关键要点

  1. 使用 Stopwatch 测量执行时间
  2. 识别瓶颈 找出耗时最多的操作
  3. 使用增量生成器 提高性能 10-100 倍
  4. 早期过滤 在 predicate 中进行语法层面的过滤
  5. 避免重复计算 使用缓存
  6. 使用 StringBuilder 构建代码
  7. 使用 BenchmarkDotNet 进行基准测试
  8. 实施性能监控 持续跟踪性能指标

最后更新: 2025-01-21

基于 MIT 许可发布