Roslyn 的 API 是非常易用的。即便如此复杂的 C# 语法,建立的复杂的 C# 语法树,还有其复杂的树遍历和修改过程,也都被其 API 包装得干净简洁。

而这背后是它的重要设计思路 —— 红绿树。


 

红绿树的影子

如果你是通过搜索找到这篇文章的,那么至少证明你调试过 Roslyn API 的使用,或者阅读过 Roslyn 的源码。因为正常使用 Roslyn 的 API 时你是看不到红绿树的,这是 Roslyn 的实现细节。但你在调试的时候可能会看到 Green 属性,或者在阅读源码时看到 GetRed 方法。


▲ 调试时看到的绿树

protected T GetRed<T>(ref T field, int slot) where T : SyntaxNode
{
var result = field; if (result == null)
{
var green = this.Green.GetSlot(slot);
if (green != null)
{
Interlocked.CompareExchange(ref field, (T)green.CreateRed(this, this.GetChildPosition(slot)), null);
result = field;
}
} return result;
}

▲ Roslyn 中获取红树的源代码

源代码摘抄自:roslyn/SyntaxNode.cs at master · dotnet/roslyn

Roslyn 的设计理念

Roslyn 一开始就将漂亮的 API 作为目标的一部分,同时还要非常高的性能;所以 Roslyn 的开发团队需要找到一种特殊的数据结构来描述语言(如 C#)的语法。这种数据结构要满足这些期望的要求:

  • 不可变(Immutable)
  • 树的形式
  • 可以容易地访问父节点和子节点
  • 可以非常容易地将任何一个节点对应到源代码文件的一段文本区间
  • 可重用(Persistent)

最后一个的英文说法是 Persistent,单词的原本意思是“可持久的,连续的”,我把它翻译为“可重用”(Reusable)。Roslyn 的设计中有一个重要的业务需求,希望能够分析源代码文件并在开发者编辑的过程中不断提供建议。也就是说,当我们连续不断地去修改源代码中的文本内容时,Roslyn 也需要具备很高的性能。如果每次编辑代码都去重新解析一次整份源代码,然后全部重新生成整个数据结构,那将是大量的性能浪费;更不可能实时去分析开发者编辑的源码。所以,在 Roslyn 的设计中,希望源代码文本改变时,整棵树中的大多数节点都是能够重复使用的(无需重新生成)。

而如果将数据结构设计成不可变的(Immutable),那么重用这些节点将会非常容易。当然不止对于 Roslyn,对其它数据来说,不可变也一样有各种好处;比如可以随时重用这份数据的实例而不用担心可能被各个不同的业务模块意外修改,比如天然是线程安全的。

那么问题来了,到底什么样的数据结构能够在同时满足以上所有的特点的前提下,同时还能设计出简单易用的 API 呢?

  • 既然要容易地访问到父节点和子节点,那么我们是先构造父节点还是子节点呢?如果先构造父节点,那子节点还没有创建出来;而先构造子节点,那父节点就没构造出来。我们要求这样的数据结构具有不可变性,所以我们不可能先把它们都构造出来再去修改它们的父子关系。
  • 还有,我们也不能随意地去为任何子节点指定新的父节点,因为子节点是不可变的。然而我们同时有希望能够在连续修改的情况下具备较高的性能,如果连修改父节点都不能办到,那也很难重用之前的节点,最终不得不再次重新生成所有的子节点。
  • 另外,如果你在源代码文件中插入了一个字符,那么这个字符后面的每一个节点对应的源代码区间都需要改变。然而这非常不利于连续修改,因为随便一个字符的插入都将导致更新大量节点中的文本区间信息。而由于不可变性,我们只能重新生成这些节点而没法儿重用它们。

于是 Roslyn 团队就折腾出了“红绿树”(Red-Green Trees)。

红绿树

红绿树并不是一棵树,而是两棵树。

绿树(the green tree)是不可变的,可重用的,没有父节点的引用。绿树的构建是自下而上的,每一个节点都保存它在文本区间中的字符个数(说通用点是宽度)。如果源代码的内容被编辑,我们只需要重新创建受编辑影响的绿树的部分;相比于重新分析整棵树,其时间复杂度只有 O(log n)。

树(the red tree)也是不可变的,是围绕绿树而建的外观(参见 外觀模式)。红树的构建是自上而下的,但红树只在需要时才会创建,而一旦编辑了源代码文件,红树就直接丢弃不用了。如果有需要,红树就会开始创建;它会根据绿树自上而下计算最新的父节点引用,计算节点最新对应的文本区间。

这两棵树设计起来协同工作,前者负责解决 Roslyn 语法分析的性能问题,后者负责对开发人员提供友好的 API 调用。由于最开始 Roslyn 团队的大佬们在会议室讨论时,前者是用红笔画的,后者是用绿笔画的,于是就合在一起称作“红绿树”。

自此,Roslyn 团队设计出的这种数据结构满足了以上所有的要求。不过,如果红树太大,每次重新生成依然会耗费比较多的性能。


参考资料

理解 Roslyn 中的红绿树(Red-Green Trees)的更多相关文章

  1. 深入理解Java中的不可变对象

    深入理解Java中的不可变对象 不可变对象想必大部分朋友都不陌生,大家在平时写代码的过程中100%会使用到不可变对象,比如最常见的String对象.包装器对象等,那么到底为何Java语言要这么设计,真 ...

  2. 理解CNN中的通道 channel

    在深度学习的算法学习中,都会提到 channels 这个概念.在一般的深度学习框架的 conv2d 中,如 tensorflow .mxnet ,channels 都是必填的一个参数. channel ...

  3. 万字长文深入理解java中的集合-附PDF下载

    目录 1. 前言 2. List 2.1 fail-safe fail-fast知多少 2.1.1 Fail-fast Iterator 2.1.2 Fail-fast 的原理 2.1.3 Fail- ...

  4. 深入理解css中的margin属性

    深入理解css中的margin属性 之前我一直认为margin属性是一个非常简单的属性,但是最近做项目时遇到了一些问题,才发现margin属性还是有一些“坑”的,下面我会介绍margin的基本知识以及 ...

  5. 深入理解css中position属性及z-index属性

    深入理解css中position属性及z-index属性 在网页设计中,position属性的使用是非常重要的.有时如果不能认识清楚这个属性,将会给我们带来很多意想不到的困难. position属性共 ...

  6. 深入理解JavaScript中创建对象模式的演变(原型)

    深入理解JavaScript中创建对象模式的演变(原型) 创建对象的模式多种多样,但是各种模式又有怎样的利弊呢?有没有一种最为完美的模式呢?下面我将就以下几个方面来分析创建对象的几种模式: Objec ...

  7. 深刻理解Java中final的作用(一):从final的作用剖析String被设计成不可变类的深层原因

    声明:本博客为原创博客,未经同意,不得转载!小伙伴们假设是在别的地方看到的话,建议还是来csdn上看吧(原文链接为http://blog.csdn.net/bettarwang/article/det ...

  8. 夯实Java基础系列12:深入理解Java中的反射机制

    本系列文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查看 https://github.com/h2pl/Java-Tutorial 喜欢的话麻烦点下 ...

  9. Java集合详解6:这次,从头到尾带你解读Java中的红黑树

    <Java集合详解系列>是我在完成夯实Java基础篇的系列博客后准备开始写的新系列. 这些文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查 ...

随机推荐

  1. nmap 端口扫描王,查看端口是否可访问,是否对外开放

    NMap,也就是Network Mapper,最早是Linux下的网络扫描和嗅探工具包. 1 安装 apt-get install nmap 2 使用,查看ip下所有对外的端口 $ sudo nmap ...

  2. 通栏banner自适应各个设备

    思路:图片不要设置为div的background,因为你设置了background-size,但是div的height没设置一样没用,除非你搞个js判断,动态刷新. 换一种思路,直接用<img& ...

  3. 记录一下我的mac的环境变量的配置参数

    #配置jdk环境export JAVA_7_HOME=/Library/java/JavaVirtualMachines/jdk1.7.0_79.jdk/Contents/Homeexport JAV ...

  4. JavaScript声明全局变量三种方式的异同

    JavaScript中声明变量很简单var(关键字)+变量名(标识符). 方式1 1 2 var test; var test = 5; 需注意的是该句不能包含在function内,否则是局部变量.这 ...

  5. int数组的下标为n

    int 类型的数组 先定义int n = 5: 定义int a[n] = {0}; 编译怎么不通过啊?n必须为常量如果想实现你说的,应该用宏#define n 5int a[n]={0};这样就可以了 ...

  6. 新东方雅思词汇---10.1、(a)esthet

    新东方雅思词汇---10.1.(a)esthet 一.总结 一句话总结: 感觉 aesthetic 英 [i:sˈθetɪk]  美 [esˈθetɪk; ɛsˈθɛtɪk]  adj. 美的:美学的 ...

  7. JSP 连接数据库

    本章节假设您已经对JDBC有一定的了解.在开始学习JSP数据库访问前,请确保JDBC环境已经正确配置. 首先,让我们按照下面的步骤来创建一个简单的表并插入几条简单的记录: 创建表 在数据库中创建一个E ...

  8. [洛谷U62358]求导函数

    U62358 求导函数 题面 给出一个n次函数\(f(x)=a_{n}x^{n}+a_{n-1}x^{n-1}+...+a_{1}x+a_0\)的各项系数\(a_n,a_{n-1}...a_1,a_0 ...

  9. PANDAS 数据合并与重塑(concat篇)

    转自:http://blog.csdn.net/stevenkwong/article/details/52528616

  10. Pandas:时间数据的季节分析

    最近在做论文的数据处理,涉及到不同年份不同季节的分析.另外还要求不同季节的数据可以单独分析. 其实思路还是比较简单的,那就在原始数据中增加一栏:季节 2013-05-21 Aotizhongxin 1 ...