Skip to content

性能优化最佳实践

本文档介绍源生成器的性能优化最佳实践,包括增量生成、早期过滤和缓存策略。


📋 文档信息

属性
难度中级 - 高级
阅读时间20 分钟
前置知识增量生成器、性能分析

🎯 学习目标

学完本文档后,你将能够:

  • ✅ 使用增量生成器提升性能
  • ✅ 实施早期过滤策略
  • ✅ 使用 record 类型支持缓存
  • ✅ 优化字符串操作和内存使用
  • ✅ 实施高级缓存策略

⚡ 使用增量生成器

1. 始终使用 IIncrementalGenerator

✅ 推荐:增量生成器

csharp
// ✅ 推荐:增量生成器
[Generator]
public class FastGenerator : IIncrementalGenerator
{
    public void Initialize(IncrementalGeneratorInitializationContext context)
    {
        // 增量管道自动缓存和优化
        var classes = context.SyntaxProvider
            .ForAttributeWithMetadataName(
                "GenerateAttribute",
                predicate: (node, _) => node is ClassDeclarationSyntax,
                transform: (ctx, _) => GetClassInfo(ctx));
        
        context.RegisterSourceOutput(classes, GenerateCode);
    }
}

// ❌ 避免:传统生成器
[Generator]
public class SlowGenerator : ISourceGenerator
{
    public void Execute(GeneratorExecutionContext context)
    {
        // 每次都重新分析所有代码,性能差
        foreach (var tree in context.Compilation.SyntaxTrees)
        {
            // ...
        }
    }
    
    public void Initialize(GeneratorInitializationContext context) { }
}

性能对比:

  • 增量生成器:只处理变化的代码,性能提升 10-100 倍
  • 传统生成器:每次都重新分析,性能差

🔍 早期过滤

2. 在 predicate 中进行语法层面的快速过滤

✅ 推荐:早期过滤

csharp
public void Initialize(IncrementalGeneratorInitializationContext context)
{
    var classes = context.SyntaxProvider
        .ForAttributeWithMetadataName(
            "GenerateAttribute",
            // ✅ 在语法层面快速过滤
            predicate: (node, _) =>
            {
                if (node is not ClassDeclarationSyntax cls)
                    return false;
                
                // 检查是否是 partial 类
                if (!cls.Modifiers.Any(m => m.IsKind(SyntaxKind.PartialKeyword)))
                    return false;
                
                // 检查是否是 public 类
                if (!cls.Modifiers.Any(m => m.IsKind(SyntaxKind.PublicKeyword)))
                    return false;
                
                return true;
            },
            transform: (ctx, _) => GetClassInfo(ctx));
}

❌ 避免:在 transform 中过滤

csharp
// ❌ 性能差:在 transform 中进行复杂过滤
var classes = context.SyntaxProvider
    .ForAttributeWithMetadataName(
        "GenerateAttribute",
        predicate: (node, _) => true, // 不过滤,所有节点都进入 transform
        transform: (ctx, _) =>
        {
            var symbol = ctx.TargetSymbol as INamedTypeSymbol;
            
            // 在这里过滤,需要语义分析,性能差
            if (symbol == null) return null;
            if (!symbol.IsPartialDefinition()) return null;
            if (symbol.DeclaredAccessibility != Accessibility.Public) return null;
            
            return GetClassInfo(ctx);
        });

📦 使用 record 类型支持缓存

3. record 类型自动实现值相等性

✅ 推荐:使用 record 类型

csharp
// ✅ record 类型自动实现值相等性,支持缓存
public record ClassInfo(
    string Name,
    string Namespace,
    ImmutableArray<PropertyInfo> Properties);

public record PropertyInfo(
    string Name,
    string Type,
    bool IsNullable);

❌ 避免:使用 class 类型

csharp
// ❌ class 类型使用引用相等性,不支持缓存
public class ClassInfo
{
    public string Name { get; set; }
    public string Namespace { get; set; }
    public List<PropertyInfo> Properties { get; set; }
}

为什么 record 更好:

  • 自动实现值相等性(EqualsGetHashCode
  • 增量生成器可以正确缓存
  • 不可变性保证线程安全

🚫 避免不必要的语义分析

4. 只在需要时进行语义分析

✅ 推荐:只在需要时进行语义分析

csharp
var classes = context.SyntaxProvider
    .ForAttributeWithMetadataName(
        "GenerateAttribute",
        predicate: (node, _) =>
        {
            // ✅ 只使用语法信息,不需要语义分析
            return node is ClassDeclarationSyntax cls &&
                   cls.Modifiers.Any(m => m.IsKind(SyntaxKind.PartialKeyword));
        },
        transform: (ctx, _) =>
        {
            // ✅ 只在这里进行语义分析
            var symbol = (INamedTypeSymbol)ctx.TargetSymbol;
            return new ClassInfo(symbol.Name);
        });

📝 使用 StringBuilder 构建代码

5. StringBuilder 性能优化

✅ 推荐:使用 StringBuilder

csharp
private string GenerateCode(ClassInfo classInfo)
{
    var sb = new StringBuilder();
    
    sb.AppendLine($"namespace {classInfo.Namespace}");
    sb.AppendLine("{");
    sb.AppendLine($"    public partial class {classInfo.Name}");
    sb.AppendLine("    {");
    
    foreach (var property in classInfo.Properties)
    {
        sb.AppendLine($"        public {property.Type} {property.Name} {{ get; set; }}");
    }
    
    sb.AppendLine("    }");
    sb.AppendLine("}");
    
    return sb.ToString();
}

❌ 避免:使用字符串拼接

csharp
// ❌ 性能差
private string GenerateCode(ClassInfo classInfo)
{
    string code = "namespace " + classInfo.Namespace + "\n{\n";
    code += "    public partial class " + classInfo.Name + "\n    {\n";
    
    foreach (var property in classInfo.Properties)
    {
        code += "        public " + property.Type + " " + property.Name + " { get; set; }\n";
    }
    
    code += "    }\n}";
    return code;
}

🎓 高级性能优化技巧

1. 使用增量值提供器(IncrementalValueProvider)

✅ 推荐:链式组合提供器

csharp
public void Initialize(IncrementalGeneratorInitializationContext context)
{
    // 步骤 1: 收集编译信息
    var compilationInfo = context.CompilationProvider
        .Select((compilation, _) => new CompilationInfo
        {
            AssemblyName = compilation.AssemblyName,
            Language = compilation.Language
        });
    
    // 步骤 2: 收集类信息
    var classInfos = context.SyntaxProvider
        .ForAttributeWithMetadataName(
            "GenerateAttribute",
            predicate: (node, _) => node is ClassDeclarationSyntax,
            transform: (ctx, _) => GetClassInfo(ctx));
    
    // 步骤 3: 组合数据
    var combined = compilationInfo.Combine(classInfos.Collect());
    
    // 步骤 4: 生成代码
    context.RegisterSourceOutput(combined, (spc, data) =>
    {
        var (compInfo, classes) = data;
        foreach (var classInfo in classes)
        {
            var code = GenerateCode(compInfo, classInfo);
            spc.AddSource($"{classInfo.Name}.g.cs", code);
        }
    });
}

❌ 避免:在 RegisterSourceOutput 中进行复杂计算

csharp
// ❌ 不好:在输出阶段进行复杂计算
context.RegisterSourceOutput(classInfos, (spc, classInfo) =>
{
    // 这些计算应该在 Select 或 Transform 中完成
    var compilation = GetCompilation(); // 昂贵操作
    var additionalInfo = AnalyzeClass(classInfo); // 昂贵操作
    
    var code = GenerateCode(classInfo, additionalInfo);
    spc.AddSource($"{classInfo.Name}.g.cs", code);
});

2. 使用 Collect() 批量处理

✅ 推荐:批量处理相关项

csharp
public void Initialize(IncrementalGeneratorInitializationContext context)
{
    var classInfos = context.SyntaxProvider
        .ForAttributeWithMetadataName(
            "GenerateAttribute",
            predicate: (node, _) => node is ClassDeclarationSyntax,
            transform: (ctx, _) => GetClassInfo(ctx));
    
    // 收集所有类信息
    var allClasses = classInfos.Collect();
    
    // 批量生成代码
    context.RegisterSourceOutput(allClasses, (spc, classes) =>
    {
        // 生成索引文件
        var indexCode = GenerateIndexFile(classes);
        spc.AddSource("Index.g.cs", indexCode);
        
        // 生成每个类的代码
        foreach (var classInfo in classes)
        {
            var code = GenerateClassCode(classInfo);
            spc.AddSource($"{classInfo.Name}.g.cs", code);
        }
    });
}

private string GenerateIndexFile(ImmutableArray<ClassInfo> classes)
{
    var sb = new StringBuilder();
    sb.AppendLine("// Auto-generated index");
    sb.AppendLine("public static class GeneratedIndex");
    sb.AppendLine("{");
    sb.AppendLine("    public static readonly string[] AllClasses = new[]");
    sb.AppendLine("    {");
    
    foreach (var classInfo in classes)
    {
        sb.AppendLine($"        \"{classInfo.Name}\",");
    }
    
    sb.AppendLine("    };");
    sb.AppendLine("}");
    
    return sb.ToString();
}

3. 使用 WithComparer 优化缓存

✅ 推荐:实现 IEquatable 和提供比较器

csharp
// 数据模型实现 IEquatable
public record ClassInfo(
    string Name,
    string Namespace,
    ImmutableArray<PropertyInfo> Properties) : IEquatable<ClassInfo>
{
    public bool Equals(ClassInfo? other)
    {
        if (other is null) return false;
        if (ReferenceEquals(this, other)) return true;
        
        return Name == other.Name &&
               Namespace == other.Namespace &&
               Properties.SequenceEqual(other.Properties);
    }
    
    public override int GetHashCode()
    {
        var hash = new HashCode();
        hash.Add(Name);
        hash.Add(Namespace);
        
        foreach (var prop in Properties)
        {
            hash.Add(prop);
        }
        
        return hash.ToHashCode();
    }
}

// 使用 WithComparer
public void Initialize(IncrementalGeneratorInitializationContext context)
{
    var classInfos = context.SyntaxProvider
        .ForAttributeWithMetadataName(
            "GenerateAttribute",
            predicate: (node, _) => node is ClassDeclarationSyntax,
            transform: (ctx, _) => GetClassInfo(ctx))
        .WithComparer(new ClassInfoComparer());
    
    context.RegisterSourceOutput(classInfos, GenerateCode);
}

public class ClassInfoComparer : IEqualityComparer<ClassInfo>
{
    public bool Equals(ClassInfo? x, ClassInfo? y)
    {
        if (ReferenceEquals(x, y)) return true;
        if (x is null || y is null) return false;
        
        return x.Equals(y);
    }
    
    public int GetHashCode(ClassInfo obj)
    {
        return obj.GetHashCode();
    }
}

4. 使用 ValueProvider 缓存昂贵的计算

csharp
public void Initialize(IncrementalGeneratorInitializationContext context)
{
    // 缓存编译级别的信息(只计算一次)
    var wellKnownTypes = context.CompilationProvider
        .Select((compilation, _) => new WellKnownTypes
        {
            StringType = compilation.GetSpecialType(SpecialType.System_String),
            Int32Type = compilation.GetSpecialType(SpecialType.System_Int32),
            ObjectType = compilation.GetSpecialType(SpecialType.System_Object),
            IEnumerableType = compilation.GetTypeByMetadataName(
                "System.Collections.Generic.IEnumerable`1")
        });
    
    var classInfos = context.SyntaxProvider
        .ForAttributeWithMetadataName(
            "GenerateAttribute",
            predicate: (node, _) => node is ClassDeclarationSyntax,
            transform: (ctx, _) => GetClassInfo(ctx));
    
    // 组合缓存的类型信息和类信息
    var combined = wellKnownTypes.Combine(classInfos);
    
    context.RegisterSourceOutput(combined, (spc, data) =>
    {
        var (types, classInfo) = data;
        var code = GenerateCode(classInfo, types);
        spc.AddSource($"{classInfo.Name}.g.cs", code);
    });
}

public record WellKnownTypes
{
    public INamedTypeSymbol StringType { get; init; }
    public INamedTypeSymbol Int32Type { get; init; }
    public INamedTypeSymbol ObjectType { get; init; }
    public INamedTypeSymbol IEnumerableType { get; init; }
}

5. 优化字符串操作

✅ 推荐:使用 StringBuilder 和 string interpolation

csharp
public string GenerateCode(ClassInfo classInfo)
{
    var sb = new StringBuilder(capacity: 1024); // 预分配容量
    
    sb.AppendLine($"namespace {classInfo.Namespace}");
    sb.AppendLine("{");
    sb.AppendLine($"    public partial class {classInfo.Name}");
    sb.AppendLine("    {");
    
    // 使用 string.Join 而不是循环拼接
    var properties = string.Join(Environment.NewLine,
        classInfo.Properties.Select(p =>
            $"        public {p.Type} {p.Name} {{ get; set; }}"));
    
    sb.AppendLine(properties);
    sb.AppendLine("    }");
    sb.AppendLine("}");
    
    return sb.ToString();
}

❌ 避免:频繁的字符串拼接

csharp
// ❌ 性能差
public string GenerateCodeSlow(ClassInfo classInfo)
{
    string code = "";
    code += $"namespace {classInfo.Namespace}\n";
    code += "{\n";
    
    foreach (var property in classInfo.Properties)
    {
        code += $"    public {property.Type} {property.Name} {{ get; set; }}\n";
    }
    
    code += "}\n";
    return code;
}

💡 关键要点

  1. 始终使用增量生成器 - 性能提升 10-100 倍
  2. 早期过滤 - 在 predicate 中进行语法层面的过滤
  3. 使用 record 类型 - 支持缓存和值相等性
  4. 避免不必要的语义分析 - 只在需要时进行
  5. 使用 StringBuilder - 构建代码时使用 StringBuilder
  6. 批量处理 - 使用 Collect() 收集数据
  7. 缓存昂贵的计算 - 使用 CompilationProvider 缓存
  8. 使用 WithComparer - 为自定义类型提供比较器

🔗 相关资源


📖 下一步

继续学习代码质量最佳实践 → 代码质量最佳实践


最后更新: 2025-01-21文档版本: 1.0

基于 MIT 许可发布