语法树性能优化
简介
本文档深入介绍 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 行) |
|---|---|---|---|
| 解析 | 小于 1ms | 5-10ms | 50-100ms |
| DescendantNodes | 小于 1ms | 2-5ms | 20-50ms |
| SyntaxWalker | 1-2ms | 5-10ms | 30-60ms |
| SyntaxRewriter | 2-3ms | 10-20ms | 50-100ms |
| 父节点访问 (1000次) | 5-10ms | 10-20ms | 30-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 文档
- SyntaxNode.DescendantNodes - 获取后代节点
- CSharpSyntaxWalker - 访问者模式
- SyntaxNode.Parent - 获取父节点
- SyntaxNode.Ancestors - 获取祖先节点
相关文档
📚 总结
性能优化的关键要点:
- 理解红绿树: 知道何时会创建红节点
- 减少遍历: 一次遍历收集所有需要的信息
- 缓存结果: 对不变的查询结果进行缓存
- 避免父节点访问: 尽量使用 Ancestors 或缓存
- 批量操作: 批量创建和修改节点
- 使用合适的工具: 根据场景选择 DescendantNodes、SyntaxWalker 或 SyntaxRewriter
返回 语法树概览 查看快速参考。
性能优化建议
- 始终先测量性能,再优化
- 使用性能分析工具找出瓶颈
- 优先优化最常执行的代码路径
- 在性能和代码可读性之间找到平衡