这是我在知乎上的回答,原文在这里:justjavac:VSCode、ATOM等开源文本编辑器的代码实现有哪些技巧和窍门?对V8研究了很多,也关注过vscode和atom的性能。我每次都会看vscode和atom的变更日志。给我印象最深的是vscode1.14的一个变更日志,doApplyEditsLinesinsertedusingsplice·Issue#351·Microsoft/monaco-editor:Donotusespliceinaloop.下图是我一年前跑的测试结果:Insertingarraywithinarraywithgap300+times。之前vscode还有一个很大的性能提升。在1.9版本中,改进了语法高亮算法。语法高亮的过程通常分为两个阶段(tokenization和render):首先将源代码分割成token,然后使用不同的主题对分割后的token进行着色。分词的过程是:从上到下逐行运行。分词器在行尾存储一些状态,用于分词下一行。这样,当用户进行编辑时,只有一小部分标记化行需要重新标记,而不是扫描整个文件内容。还有一种情况,当前行的输入会影响到后面(甚至是前面)行,这时候就会用到endstate:vscode1.9之前的版本如何分词?比如上面的代码:在vscode中这样存储:tokens=[{startIndex:0,type:'keyword.js'},{startIndex:8,type:''},{startIndex:9,type:'identifier.js'},{startIndex:11,type:'delimiter.paren.js'},{startIndex:12,type:'delimiter.paren.js'},{startIndex:13,type:''},{startIndex:14,type:'delimiter.curly.js'},]{startIndex:0,type:'keyword.js'}表示从0开始的token是关键字。VSCode团队发博称,这在Chrome中占用648字节,因此存储这样的对象在内存方面非常昂贵(每个对象实例必须为其原型、属性列表等预留空间)。为15个字符存储648个字节是不可接受的。因此,vscode使用二进制来存储标记://01234map=['','keyword.js','identifier.js','delimiter.paren.js','delimiter.curly.js'];tokens=[{startIndex:0,type:1},{startIndex:8,type:0},{startIndex:9,type:2},{startIndex:11,type:3},{startIndex:12,type:3},{startIndex:13,type:0},{startIndex:14,type:4},]和上面的写法相比,只是把类型从字符串变成了数字,本质上并没有节省多少内存.不过不用担心,vscode也有黑科技。我们都知道JavaScript使用IEEE-754标准来存储尾数为53位的双精度浮点数。可以在不损失精度的情况下处理的最大整数是2^53-1。所以vscode使用48big进行编码:用32bit存放startIndex,16bit存放type。于是上面的对象在vscode种被存储为:tokens=[//typestartIndex4294967296,//0000000000000001000000000000000000000000000000008,//0000000000000000000000000000000000000000000010008589934601,//00000000000000100000000000000000000000000000100112884901899,//00000000000000110000000000000000000000000000101112884901900,//00000000000000110000000000000000000000000000110013,//00000000000000000000000000000000000000000000110117179869198,//000000000000010000000000000000000000000000001110]每个数字是64bit(8字节),一共是7个数字,存储这些元素一共需要7*8=56字节,再加上数组的额外开销共需要104bytes,only1/6oftheprevious648bytes.TherenderingofthethemeusestheTriedatastructure.Anyonewhohaslearned《数据结构》knowsthis,anditisnotamagicskill,soIwon’texpandit.Thisisallwithvscode1.9releasedinMarch2017.InMarchofthisyear,vscoderewroteTextBuffer.Whenusersusetheeditor,mostofthetimeistowritenewcodes,modifyoldcodes,andedittextafterall.对于高性能的文本操作,vscode最初尝试使用C++来编写。毕竟C++的性能比JavaScript高很多,但事实并不理想。使用C++确实节省内存,但是当使用C++模块时,JavaScript需要在C++和C++之间进行多次往返,这大大拖慢了vscode的性能。vscode团队的灵感来自VyacheslavEgorov的文章Maybeyoudon'tneedRustandWASMtospeedyourJS,如何充分压榨V8引擎的性能。mrale.ph的博客几乎每篇文章都看了,很经典,也很难看懂。大多数编辑器都是基于行的。程序员逐行编写代码,编译器提供基于行的反馈信息,堆栈跟踪包含行号,标记化引擎逐行运行......在早期版本的vscode中,每一行代码都直接存储为数组中的字符串。但是这种方式存在一些问题:大文件无法打开,因为将所有内容读入一个数组可能会导致内存不足。即使文件不大,行数过多也打不开。例如,用户无法打开35MB的文件。根本原因是文件行数太多,1370万行。引擎将为ModelLine每行和对象使用大约40-60个字节,因此整个数组使用大约600MB的内存来存储文档。也就是说,打开这个35M的文件,需要600M的内容,20倍!!!另一个问题是速度。为了构建这个数组,内容必须用换行符分开,这样你每行得到一个字符串对象。于是vscode开始寻找新的数据结果,最终选择了Piece表。不知道为什么这么晚才选择piecetable,但是要知道piecetable在微软的officeword中已经使用很久了。我也是在一次java看wordjar包源码的时候第一次学习到片表数据结构的。推荐几篇文章进一步阅读:Emacseditorbufferpaper:Flexchain:Aneditablesequenceanditsgap-bufferimplementation2004-04-05piecetable:DataStructuresforTextSequences1998-06-10Ropes:AnAlternativetoStrings1995-12目前,三种主要的编辑方法是gapbuffer、rope和piecetable。我最近很少使用Atom。上一次让我兴奋的是:TheStateofAtom'sPerformance。2017年6月,Atom使用piecetable数据结构并用C++重新实现了文本缓冲区:Atom'snewconcurrency-friendlybufferimplementation。比vscode早了半年,为什么还是这么慢???Atom使用V8的自定义快照(snapshot)来提升启动性能,最终去除了影响性能的jQuery和自定义元素。就连V8的Atom也更新了DOM渲染方式:Anewapproachtotextrendering,这个新算法包括了一个类似React的vdom。从issue来看,这是一个大工程,包含了近百个任务。系列优化,官方表示:我们使加载Atom的速度提高了近50%,并且快照是一个重要的工具,可以实现一些原本不可能的优化。我们使Atom速度提高了50%,而快照做出了很大贡献。(PS:肯定是用了假的Atom)不过snapshot确实是V8的神器。Nodejs也看到了Atom的成果,在2017-11-16开了一个issue:使用V8快照加速Node.js启动Issue#17058·nodejs/node。这个在我之前的专栏:Node.js新计划:使用V8快照,启动速度提升8倍中有介绍。最近对Atom的关注是atom/xray。知乎上也有相关讨论,atom开发的下一代编辑器(atom已经定义为上一代编辑器了吗?)。大概是一种“大号没用,小号又用来练”的感觉吧。文本处理使用copy-on-writeCRDT值得学习:如果你一直关注Atom,你应该熟悉CRDT。Atom的多人实时协同编辑插件https://teletype.atom.io/就是使用的CRDT。CRDT的全称:Conflict-FreeReplicatedDataTypes,强行翻译成“Conflict-FreeReplicatedDataTypes”。CRDT论文:ConvergentandCommutativeReplicatedDataTypes综合研究2011-01-13CAP定理:在分布式系统中,至多只能满足一致性(Consistency)、可用性(Availability)和分区容错性(Partitiontolerance)同一时间。三分之二。很多分布式系统都抛弃了C(consistency):允许在某些时刻不一致,二是要求系统满足最终一致性。这也是目前很多nosql数据库所追求的方式(另一种是传统的数据库系统,符合ACID特性,放弃了A(availability),这种系统叫强一致性)。在最终一致性分布式系统中,最基本的问题之一就是,应该使用什么样的数据结构来保证最终一致性?答案是CRDT。文章atom/teletype-crdt只是提纲,里面的每个知识点都可以三天三夜展开。
