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. cordova安卓sdk

    Android SDK在线更新镜像服务器来下载安装: 1.北京化工大学镜像服务器地址: IPv4: ubuntu.buct.edu.cn/ 端口:80 IPv4: ubuntu.buct.cn/ 端口 ...

  2. SpringBoot+MyBatis简单数据访问应用

    因为实习用的是MyBatis框架,所以写一篇关于SpringBoot整合MyBatis框架的总结. 一,Pom文件 <?xml version="1.0" encoding= ...

  3. day32 Python与金融量化分析(二)

    第一部分:金融与量化投资 股票: 股票是股份公司发给出资人的一种凭证,股票的持有者就是股份公司的股东. 股票的面值与市值 面值表示票面金额 市值表示市场价值 上市/IPO: 企业通过证券交易所公开向社 ...

  4. leetcode 559. Maximum Depth of N-ary Tree

    Given a n-ary tree, find its maximum depth. The maximum depth is the number of nodes along the longe ...

  5. C++:哈希

    1.基本概念 哈希一般用来快速查找,通过hash函数将输入的键值(key)映射到某一个地址,然后就可以获得该地址的内容. 同样,如果要储存一对值(键值和数据),则也是通过hash函数获得地址来存入.见 ...

  6. Convert(varchar(8),Getdate(),108) 什么意思

    100 或者 0 mon dd yyyy hh:miAM (或者 PM) 101 mm/dd/yy 102 yy.mm.dd 103 dd/mm/yy 104 dd.mm.yy 105 dd-mm-y ...

  7. Prism 4 文档 ---第2章:初始化Prism应用程序

     这一章节介绍Prism应用程序启动和运行时发生的内容.Prism应用程序在启动时需要有注册和配置的过程,这就是所谓的自自启动程序. 什么是自启动引导程序?     引导程序是一个类,它负责使用Pri ...

  8. ZOJ 2971 Give Me the Number (模拟,字符数组的清空+map)

    Give Me the Number Time Limit: 2 Seconds      Memory Limit: 65536 KB Numbers in English are written ...

  9. 剑指offer--38.左旋转字符串

    时间限制:1秒 空间限制:32768K 热度指数:173814 本题知识点: 字符串 题目描述 汇编语言中有一种移位指令叫做循环左移(ROL),现在有个简单的任务,就是用字符串模拟这个指令的运算结果. ...

  10. Linux虚拟机基本操作

    ---恢复内容开始---  一.输入法调整 实现步骤:Application ------> System Tools ------>Settings ------>Rejion&a ...