Skip to content

语法树性能优化

简介

本文档深入介绍 Roslyn 语法树的性能优化技术。你将学习红绿树架构、性能考虑因素、优化技巧和最佳实践,帮助你编写高效的代码分析和生成工具。

适合人群: 高级开发者
预计阅读时间: 90 分钟
前置知识: 语法树基础、遍历和修改操作


📋 本文导航

章节难度阅读时间链接
红绿树架构🔴 高级25 分钟查看
性能考虑因素🟡 中级15 分钟查看
减少遍历次数🟡 中级15 分钟查看
缓存策略🟡 中级15 分钟查看
避免不必要的节点创建🔴 高级10 分钟查看
最佳实践 vs 反模式🟡 中级20 分钟查看

返回: 语法树概览 | 上一篇: 修改操作


🔴 红绿树架构

Roslyn 使用一种称为"红绿树"的内部结构来优化内存使用和性能。

什么是红绿树?

红绿树是 Roslyn 的核心设计,将语法树分为两层:

  • 绿树(Green Tree): 不可变的、共享的节点,不包含位置信息
  • 红树(Red Tree): 包含位置信息的节点,是我们通常操作的节点

绿树(Green Tree)

特点:

  • 不可变且可共享
  • 不包含位置信息
  • 不包含父节点引用
  • 内存效率高

示例:

csharp
// 这两个类声明在绿树层可能共享相同的节点
var code1 = "public class Person { }";
var code2 = "public class Person { }";

var tree1 = CSharpSyntaxTree.ParseText(code1);
var tree2 = CSharpSyntaxTree.ParseText(code2);

// 绿节点可以被共享
// 这节省了内存

红树(Red Tree)

特点:

  • 包含位置信息(Span)
  • 包含父节点引用
  • 按需创建
  • 提供导航功能

示例:

csharp
var code = "public class Person { }";
var tree = CSharpSyntaxTree.ParseText(code);
var root = tree.GetRoot();

var classDecl = root.DescendantNodes()
    .OfType<ClassDeclarationSyntax>()
    .First();

// 这是一个红节点
Console.WriteLine($"位置: {classDecl.Span}");        // 有位置信息
Console.WriteLine($"父节点: {classDecl.Parent}");     // 有父节点引用

红绿树的协作

性能影响

内存优化:

csharp
// 相同的代码结构可以共享绿节点
var methods = new[]
{
    "public void Method1() { }",
    "public void Method2() { }",
    "public void Method3() { }"
};

// 这些方法的结构相似,可以共享很多绿节点
foreach (var method in methods)
{
    var tree = CSharpSyntaxTree.ParseText(method);
    // 内部会重用绿节点
}

访问父节点的代价:

csharp
var classDecl = root.DescendantNodes()
    .OfType<ClassDeclarationSyntax>()
    .First();

// ❌ 不好:频繁访问父节点会创建很多红节点
for (int i = 0; i < 1000; i++)
{
    var parent = classDecl.Parent;
    var grandParent = parent.Parent;
}

// ✅ 好:缓存父节点引用
var parent = classDecl.Parent;
var grandParent = parent.Parent;
for (int i = 0; i < 1000; i++)
{
    // 使用缓存的引用
}

关键要点

  • 绿树节点可以被多个红树节点共享,节省内存
  • 访问父节点会创建红节点,有性能开销
  • 位置信息是按需计算的
  • 理解红绿树有助于编写高效的代码

🟡 性能考虑因素

编写高性能的语法树操作代码需要考虑多个因素。

性能影响因素图

常见性能陷阱

1. 重复遍历

csharp
// ❌ 不好:多次遍历
var classes = root.DescendantNodes().OfType<ClassDeclarationSyntax>();
var methods = root.DescendantNodes().OfType<MethodDeclarationSyntax>();
var properties = root.DescendantNodes().OfType<PropertyDeclarationSyntax>();

// ✅ 好:一次遍历
var allNodes = root.DescendantNodes().ToList();
var classes = allNodes.OfType<ClassDeclarationSyntax>();
var methods = allNodes.OfType<MethodDeclarationSyntax>();
var properties = allNodes.OfType<PropertyDeclarationSyntax>();

2. 频繁访问父节点

csharp
// ❌ 不好:每次都访问 Parent
foreach (var method in methods)
{
    if (method.Parent is ClassDeclarationSyntax classDecl)
    {
        // 处理
    }
}

// ✅ 好:使用 Ancestors
foreach (var method in methods)
{
    var classDecl = method.Ancestors()
        .OfType<ClassDeclarationSyntax>()
        .FirstOrDefault();
    if (classDecl != null)
    {
        // 处理
    }
}

3. 不必要的节点创建

csharp
// ❌ 不好:创建临时节点
for (int i = 0; i < 100; i++)
{
    var tempNode = SyntaxFactory.IdentifierName($"temp{i}");
    // 只使用一次就丢弃
}

// ✅ 好:重用节点或延迟创建
var nodes = new List<IdentifierNameSyntax>();
for (int i = 0; i < 100; i++)
{
    if (needsNode(i))
    {
        nodes.Add(SyntaxFactory.IdentifierName($"temp{i}"));
    }
}

性能测试基准

操作小文件 (小于 100 行)中文件 (小于 1000 行)大文件 (大于 5000 行)
解析小于 1ms5-10ms50-100ms
DescendantNodes小于 1ms2-5ms20-50ms
SyntaxWalker1-2ms5-10ms30-60ms
SyntaxRewriter2-3ms10-20ms50-100ms
父节点访问 (1000次)5-10ms10-20ms30-50ms

🟡 减少遍历次数

最有效的优化是减少遍历语法树的次数。

一次遍历收集多种信息

csharp
// ❌ 不好:多次遍历
var publicClasses = root.DescendantNodes()
    .OfType<ClassDeclarationSyntax>()
    .Where(c => c.Modifiers.Any(m => m.IsKind(SyntaxKind.PublicKeyword)));

var publicMethods = root.DescendantNodes()
    .OfType<MethodDeclarationSyntax>()
    .Where(m => m.Modifiers.Any(m => m.IsKind(SyntaxKind.PublicKeyword)));

var publicProperties = root.DescendantNodes()
    .OfType<PropertyDeclarationSyntax>()
    .Where(p => p.Modifiers.Any(m => m.IsKind(SyntaxKind.PublicKeyword)));

// ✅ 好:一次遍历
var allNodes = root.DescendantNodes().ToList();

var publicClasses = allNodes
    .OfType<ClassDeclarationSyntax>()
    .Where(c => c.Modifiers.Any(m => m.IsKind(SyntaxKind.PublicKeyword)));

var publicMethods = allNodes
    .OfType<MethodDeclarationSyntax>()
    .Where(m => m.Modifiers.Any(m => m.IsKind(SyntaxKind.PublicKeyword)));

var publicProperties = allNodes
    .OfType<PropertyDeclarationSyntax>()
    .Where(p => p.Modifiers.Any(m => m.IsKind(SyntaxKind.PublicKeyword)));

使用 SyntaxWalker 一次收集

点击查看 SyntaxWalker 优化示例
csharp
public class ComprehensiveAnalyzer : CSharpSyntaxWalker
{
    public List<ClassDeclarationSyntax> PublicClasses { get; } = new();
    public List<MethodDeclarationSyntax> PublicMethods { get; } = new();
    public List<PropertyDeclarationSyntax> PublicProperties { get; } = new();
    
    public override void VisitClassDeclaration(ClassDeclarationSyntax node)
    {
        if (node.Modifiers.Any(m => m.IsKind(SyntaxKind.PublicKeyword)))
        {
            PublicClasses.Add(node);
        }
        base.VisitClassDeclaration(node);
    }
    
    public override void VisitMethodDeclaration(MethodDeclarationSyntax node)
    {
        if (node.Modifiers.Any(m => m.IsKind(SyntaxKind.PublicKeyword)))
        {
            PublicMethods.Add(node);
        }
        base.VisitMethodDeclaration(node);
    }
    
    public override void VisitPropertyDeclaration(PropertyDeclarationSyntax node)
    {
        if (node.Modifiers.Any(m => m.IsKind(SyntaxKind.PublicKeyword)))
        {
            PublicProperties.Add(node);
        }
        base.VisitPropertyDeclaration(node);
    }
}

// 使用:只遍历一次
var analyzer = new ComprehensiveAnalyzer();
analyzer.Visit(root);

// 现在可以访问所有收集的信息
Console.WriteLine($"公共类: {analyzer.PublicClasses.Count}");
Console.WriteLine($"公共方法: {analyzer.PublicMethods.Count}");
Console.WriteLine($"公共属性: {analyzer.PublicProperties.Count}");

限制遍历深度

csharp
// 只遍历类级别,不进入方法体
var classLevelNodes = root.DescendantNodes(
    descendIntoChildren: node => !(node is MethodDeclarationSyntax));

// 只遍历到第 3 层
int maxDepth = 3;
var limitedNodes = root.DescendantNodes(
    descendIntoChildren: node => GetDepth(node) < maxDepth);

🟡 缓存策略

合理的缓存可以显著提升性能。

缓存查询结果

csharp
public class CachedSyntaxAnalyzer
{
    private readonly SyntaxNode _root;
    private List<ClassDeclarationSyntax> _classes;
    private List<MethodDeclarationSyntax> _methods;
    
    public CachedSyntaxAnalyzer(SyntaxNode root)
    {
        _root = root;
    }
    
    public IEnumerable<ClassDeclarationSyntax> GetClasses()
    {
        if (_classes == null)
        {
            _classes = _root.DescendantNodes()
                .OfType<ClassDeclarationSyntax>()
                .ToList();
        }
        return _classes;
    }
    
    public IEnumerable<MethodDeclarationSyntax> GetMethods()
    {
        if (_methods == null)
        {
            _methods = _root.DescendantNodes()
                .OfType<MethodDeclarationSyntax>()
                .ToList();
        }
        return _methods;
    }
}

// 使用
var analyzer = new CachedSyntaxAnalyzer(root);

// 第一次调用会遍历树
var classes1 = analyzer.GetClasses();

// 后续调用使用缓存
var classes2 = analyzer.GetClasses(); // 快速
var classes3 = analyzer.GetClasses(); // 快速

使用 Lazy<T> 延迟初始化

csharp
public class LazySyntaxAnalyzer
{
    private readonly SyntaxNode _root;
    private readonly Lazy<List<ClassDeclarationSyntax>> _classes;
    private readonly Lazy<List<MethodDeclarationSyntax>> _methods;
    
    public LazySyntaxAnalyzer(SyntaxNode root)
    {
        _root = root;
        _classes = new Lazy<List<ClassDeclarationSyntax>>(
            () => _root.DescendantNodes()
                .OfType<ClassDeclarationSyntax>()
                .ToList());
        
        _methods = new Lazy<List<MethodDeclarationSyntax>>(
            () => _root.DescendantNodes()
                .OfType<MethodDeclarationSyntax>()
                .ToList());
    }
    
    public IEnumerable<ClassDeclarationSyntax> Classes => _classes.Value;
    public IEnumerable<MethodDeclarationSyntax> Methods => _methods.Value;
}

缓存父节点引用

csharp
// ❌ 不好:重复访问父节点
foreach (var method in methods)
{
    for (int i = 0; i < 100; i++)
    {
        var parent = method.Parent; // 每次都创建红节点
    }
}

// ✅ 好:缓存父节点
var methodParents = methods
    .ToDictionary(m => m, m => m.Parent);

foreach (var method in methods)
{
    var parent = methodParents[method];
    for (int i = 0; i < 100; i++)
    {
        // 使用缓存的父节点
    }
}

缓存失效策略

csharp
public class SmartCachedAnalyzer
{
    private SyntaxNode _root;
    private List<ClassDeclarationSyntax> _classes;
    private int _rootHashCode;
    
    public void UpdateRoot(SyntaxNode newRoot)
    {
        if (newRoot.GetHashCode() != _rootHashCode)
        {
            _root = newRoot;
            _rootHashCode = newRoot.GetHashCode();
            _classes = null; // 清除缓存
        }
    }
    
    public IEnumerable<ClassDeclarationSyntax> GetClasses()
    {
        if (_classes == null)
        {
            _classes = _root.DescendantNodes()
                .OfType<ClassDeclarationSyntax>()
                .ToList();
        }
        return _classes;
    }
}

🔴 避免不必要的节点创建

创建语法节点有开销,应该避免不必要的创建。

条件创建

csharp
// ❌ 不好:总是创建节点
var newMethod = CreateMethod();
if (ShouldAddMethod())
{
    classDecl = classDecl.AddMembers(newMethod);
}

// ✅ 好:只在需要时创建
if (ShouldAddMethod())
{
    var newMethod = CreateMethod();
    classDecl = classDecl.AddMembers(newMethod);
}

批量创建

csharp
// ❌ 不好:逐个添加
var classDecl = originalClass;
foreach (var propertyName in propertyNames)
{
    var property = CreateProperty(propertyName);
    classDecl = classDecl.AddMembers(property);
}

// ✅ 好:批量添加
var properties = propertyNames
    .Select(name => CreateProperty(name))
    .ToArray();

var classDecl = originalClass.AddMembers(properties);

重用节点

csharp
// 可以重用的节点
var publicModifier = SyntaxFactory.Token(SyntaxKind.PublicKeyword);
var intType = SyntaxFactory.PredefinedType(
    SyntaxFactory.Token(SyntaxKind.IntKeyword));

// 在多个地方重用
var method1 = SyntaxFactory.MethodDeclaration(intType, "Method1")
    .WithModifiers(SyntaxFactory.TokenList(publicModifier));

var method2 = SyntaxFactory.MethodDeclaration(intType, "Method2")
    .WithModifiers(SyntaxFactory.TokenList(publicModifier));

🟡 最佳实践 vs 反模式

最佳实践

✅ 1. 一次遍历收集多种信息

csharp
var allNodes = root.DescendantNodes().ToList();
var classes = allNodes.OfType<ClassDeclarationSyntax>();
var methods = allNodes.OfType<MethodDeclarationSyntax>();

✅ 2. 缓存常用查询结果

csharp
private readonly Lazy<List<ClassDeclarationSyntax>> _classes;

✅ 3. 使用 OfType<T> 而非 Where + is

csharp
// 好
var classes = nodes.OfType<ClassDeclarationSyntax>();

// 不好
var classes = nodes.Where(n => n is ClassDeclarationSyntax)
    .Cast<ClassDeclarationSyntax>();

✅ 4. 限制遍历深度

csharp
var nodes = root.DescendantNodes(
    descendIntoChildren: node => !(node is MethodDeclarationSyntax));

✅ 5. 使用 SyntaxWalker 进行复杂分析

csharp
public class MyAnalyzer : CSharpSyntaxWalker
{
    // 一次遍历收集所有信息
}

反模式

❌ 1. 重复遍历

csharp
// 不要这样做
var classes = root.DescendantNodes().OfType<ClassDeclarationSyntax>();
var methods = root.DescendantNodes().OfType<MethodDeclarationSyntax>();

❌ 2. 频繁访问父节点

csharp
// 不要这样做
for (int i = 0; i < 1000; i++)
{
    var parent = node.Parent;
}

❌ 3. 不缓存 LINQ 查询结果

csharp
// 不要这样做
var query = root.DescendantNodes().OfType<ClassDeclarationSyntax>();
foreach (var cls in query) { } // 第一次枚举
foreach (var cls in query) { } // 第二次枚举(重新遍历)

❌ 4. 创建不必要的临时节点

csharp
// 不要这样做
for (int i = 0; i < 100; i++)
{
    var temp = SyntaxFactory.IdentifierName("temp");
    // 只使用一次
}

❌ 5. 逐个修改节点

csharp
// 不要这样做
var current = classDecl;
foreach (var property in properties)
{
    current = current.AddMembers(property);
}

// 应该批量添加
var newClass = classDecl.AddMembers(properties.ToArray());

性能对比表

操作反模式最佳实践性能提升
多次查询每次都遍历缓存结果10-50x
父节点访问重复访问 Parent缓存引用5-10x
LINQ 查询多次枚举ToList()2-5x
节点创建逐个创建批量创建2-3x
遍历深度全树遍历限制深度2-10x

🔗 相关 API

核心 API 文档

相关文档


📚 总结

性能优化的关键要点:

  1. 理解红绿树: 知道何时会创建红节点
  2. 减少遍历: 一次遍历收集所有需要的信息
  3. 缓存结果: 对不变的查询结果进行缓存
  4. 避免父节点访问: 尽量使用 Ancestors 或缓存
  5. 批量操作: 批量创建和修改节点
  6. 使用合适的工具: 根据场景选择 DescendantNodes、SyntaxWalker 或 SyntaxRewriter

返回 语法树概览 查看快速参考。


性能优化建议

  • 始终先测量性能,再优化
  • 使用性能分析工具找出瓶颈
  • 优先优化最常执行的代码路径
  • 在性能和代码可读性之间找到平衡

基于 MIT 许可发布