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. Class 的基本语法

    简介 JavaScript 语言中,生成实例对象的传统方法是通过构造函数.下面是一个例子. function Point(x, y) { this.x = x; this.y = y; } Point ...

  2. mac下cordova的ios-deploy安装问题

    mac下进行cordova项目编译部署到ios设备,这个时候需要安装ios-deploy,会失败: npm WARN lifecycle ios-deploy@1.8.6~preinstall: ca ...

  3. javascript中的定时器

    本文地址:[http://www.xiabingbao.com/javascript/2015/04/20/javascript-timer/] 在以前的文章[javascript中的定时器]中,简单 ...

  4. server2012/win8 卸载.net framework 4.5后 无法进入系统桌面故障解决

    故障:服务器装的是windows2012 standard(2012版本从低到高依次为Foundation.Essentials.StandardDatacenter,以及它们的升级版R2),由于要安 ...

  5. JXLS导出Excel(模板导出)

    1.导包 在pom.xml中加入依赖如下: <dependency> <groupId>org.jxls</groupId> <artifactId>j ...

  6. 1-15-1 RAID磁盘阵列的原理和搭建

    大纲: 1.1-1-企业级RAID磁盘阵列 RAID磁盘阵列的原理 RAID0,1,5,10的搭建 硬件RAID卡 1.2-1-使用廉价的磁盘搭建RAID磁盘阵列 实战-配置RAID0带区卷 ==== ...

  7. 《OpenCL编程指南》之 与Direct3D互操作

    介绍OpenCL与D3D 10之间的互操作. 1.初始化OpenCL上下文实现Direct3D互操作 OpenCL共享由pragma cl_khr_d3d10_sharing启用: #pragma O ...

  8. 连接GitHub的方法

    连接到GitHub 首先在本地创建 ssh key: ssh-keygen -t rsa -C "your_email@youremail.com" 后面的 your_email@ ...

  9. T-sql 中 truncate 、delete与drop操作数据库的区别

    1.truncate和不带where子句的delete.以及drop都会删除表内的数据. 2.drop.truncate都是DDL语句(数据定义语言),执行后会自动提交. 具体使用语法如下: --删除 ...

  10. SQL Plus常用命令

    1.常用命令:显示当前用户名:show user登录到数据库:conn[etc] 用户名/密码@网络[as sysdba / as sysoper]   简单写法:conn 用户名/密码    con ...