结束语 | 实战是唯一标准!

你好,我是宫文学。

转眼之间,“编译原理实战课”计划中的内容已经发布完毕了。在这季课程中,你的感受如何?有了哪些收获?遇到了哪些困难?

很多同学可能会觉得这一季的课程比上一季的“编译原理之美”要难一些。不过为什么一定要推出这么一门课,来研究实际编译器的实现呢?这是因为我相信,实战是检验你是否掌握了编译原理的唯一标准,也是学习编译原理的真正目标。

计算机领域的工程性很强。这决定了我们学习编译原理,不仅仅是掌握理论,而是要把它付诸实践。在我们学习编译原理的过程中,如果遇到内心有疑惑的地方,那不妨把实战作为决策的标准。

这些疑惑可能包括很多,比如:

  • 词法分析和语法分析工具,应该手写,还是用工具生成?
  • 应该用LL算法,还是LR算法?
  • 后端应该用工具,还是自己手写?
  • 我是否应该学习后端?
  • IR应该用什么数据结构?
  • 寄存器分配采用什么算法比较好?
  • ……

上述问题,如果想在教科书里找到答案,哪怕是“读万卷书”,也是比较难的。而换一个思路,“行万里路”,那就很容易了。你会发现每种语言,因为其适用的领域和设计的目标不同,对于上述问题会采用不同的技术策略,而每种技术策略都有它的道理。从中,你不仅仅可以为上述问题找到答案,更重要的是学会权衡不同技术方案的考虑因素,从而对知识点活学活用起来。

我们说实战是标准。那你可能会反问,难道掌握基础理论和原理就不重要了吗?这是在很多领域都在争论的一个话题:理论重要,还是实践重要。

理论重要,还是实践重要?

理论和原理当然重要,在编译原理中也是如此。形式语言有关的理论,以及前端、中端和后端的各个经典算法,构筑了编译原理这门课坚实的理论基础。

但是,在出现编译原理这门课之前,在出现龙书虎书之前,工程师们已经在写编译器了。

你在工作中,有时候就会遇到理论派和实践派之争。举例来说,有时候从理论角度,某一个方案会“看上去很美”。那到底是否采用该方案呢?这个时候,就需要拿实践来说话了。

我拿Linux内核的发展举个例子。当年Linus推出Linux内核的时候,并没有采用学术界推崇的微内核架构,为此Linus还跟Minix的作者有一场著名的辩论。而实践证明,Linux内核发展得很成功,而GNU的另一个采用微架构的内核Hurd发展了20多年还没落地。

客观地说,Linux内核后来也吸收了很多微内核的设计理念。而声称采用微内核架构的Windows系统和macOS系统,其实在很多地方也已经违背了微内核的原则,而具备Linux那样的单内核的特征。之所以有上述的融合,其实都是一个原因,就是为了得到更好的实用效果。所以,实践会为很多历史上的争论划上句号。

在编译技术和计算机语言设计领域,也存在着很多的理论与实践之争。比如,理论上,似乎函数式编程更简洁、更强大,学术界也很偏爱它,但是纯函数的编程语言,至今没有成为主流,这是为什么呢?

再比如,是否一定要把龙书虎书都读明白,才算学会了编译原理呢?

再进一步,如果你使用编译技术的时候,遇到一个实际的问题,是跟着龙书、虎书还有各种课本走,还是拿出一个能解决问题的方案就行?

在课程里,我鼓励你抛弃一些传统上学习编译原理的困扰。如果龙书、虎书看不明白,那也不用过于纠结,这并不会挡住你学习的道路。多看实际的编译器,多自己动手实践,在这个过程中,你自然会明白课本里原来不知所云的知识点。

那么如何以实践为指导,从而具备更好的技术方案鉴别力呢?在本课程里,我们有三个重点。包括研究常用语言的编译器、从计算机语言设计的高度来理解编译原理,以及从运行时的实现来理解编译原理。

对于你所使用的语言,应该把它的编译器研究透

这门课程的主张是,你最好把自己所使用语言的编译器研究透。这个建议有几个理由。

第一,因为这门语言是你所熟悉的,所以你研究起来速度会更快。比如,可以更快地写出测试用的程序。并且,由于很多语言的编译器都已经实现了自举,比如说Go语言和Java语言的编译器,所以你可以更快地理解源代码,以及对编译器本身做调试。

第二,这门语言的编译器所采用的实现技术,一定是体现了该语言的特性的。比如V8会强调解析速度快,Java编译器要支持注解特性等,值得你去仔细体会。

第三,研究透编译器,会加深你对这门语言的理解。比如说,你了解清楚了Java的编译器是如何处理泛型的,那你就会彻底理解Java泛型机制的优缺点。而C++的模板机制,对于学习C++的同学是有一定挑战的。但一旦你把它在编译期的实现机制弄明白,就会彻底掌握模板机制。我也计划在后续用一篇加餐,把C++的模板机制给你拆解一下。

那么,既然编译器是为具体语言服务的,所以,我们也在课程里介绍了计算机语言设计所考虑的那些关键因素,以及它们对编译技术的影响。

从计算机语言设计的高度,去理解编译技术

在课程里你已经体会到了,语言设计的不同,必然导致采用编译技术不同。

其实,从计算机语言设计的高度上看,编译器只是实现计算机语言的一块底层基石。计算机语言设计本身有很多的研究课题,比如类型系统、所采用的编程范式、泛型特性、元编程特性等等,我们在课程里有所涉猎,但并没有在理论层面深挖。有些学校会从这个方向上来培养博士生,他们会在理论层面做更深入的研究。

什么样的计算机语言是一个好的设计?这是一个充满争议的话题,我们这门课程尽量不参与这个话题的讨论。我们的任务,是要知道当采用不同的语言设计时,应该如何运用编译技术来落地。特别是,要了解自己所使用的语言的实现机制

如果说计算机语言设计,是一种偏理论性的视角,那么程序具体的运行机制,则是更加注重落地的一种视角。

从程序运行机制的角度,去理解编译技术

学习编译原理的一个挑战,就在于你必须真正理解程序是如何运行的,以及程序都可以有哪几种运行方式。这样,你才能理解如何生成服务于这种运行机制的目标代码。

最最基础的,你需要了解像C语言这样的编译成机器码直接运行的语言,它的运行机制是怎样的。代码放在哪里,又是如何一步步被执行的。在执行过程中,栈是怎么变化的。函数调用的过程中,都发生了些什么事情。什么数据是放在栈里的,什么数据是放在堆里的,等等。

在此基础上,如果从C语言换成C++呢?C++多了个对象机制,那对象在内存里是一个什么结构?多重继承的时候是一个什么结构?在存在多态的时候,如何实现方法的正确绑定?这些C++比C语言多出来的语义,你也要能够在运行时机制中把它弄清楚。

再进一步,到了Go语言,仍然是编译成机器码运行的,但跟C和C++又有本质区别。因为Go语言的运行时里包含了垃圾收集机制和并发调度机制,这两个机制要跟你的程序编译成的代码互相配合,所以编译器生成的目标代码里要体现内存管理和并发这两大机制。像Go语言这种特殊的运行机制,还导致了跨语言调用的难度。用Go语言调用C语言的库,要有一定的转换和开销。

然后呢,语言运行时的抽象度进一步增加。到了Java语言,就用到一个虚拟机。字节码也正式登台亮相。你需要知道栈机和寄存器机这两种不同的运行字节码的解释器,也要知道它们对应的字节码的差别。而为了提升运行速度,JIT、分层编译和逆优化机制又登场,栈上替换(OSR)技术也产生。这个时候,你需要理解解释执行和运行JIT生成的本地代码,是如何无缝衔接的。这个时候的栈桢,又有何不同。

然后是JavaScript的运行时机制,就更加复杂了。V8不仅具备JVM的那些能力,在编译时还要去推断变量的类型,并且通过隐藏类的方式安排对象的内存布局,以及通过内联缓存的技术去加快对象属性的访问速度。

这样从最简单的运行时,到最复杂的虚拟机,你都能理解其运行机制的话,你其实不仅知道在不同场景下如何使用编译技术,甚至可以参与虚拟机等底层软件的研发了。

不再是谈论,来参与实战吧!

今天,我们学习编译原理,目标不能放在考试考多少分上。中国的技术生态,使得我们已经能够孕育自己的编译器、自己的语言、自己的虚拟机。方舟编译器已经带了个头。我想,中国不会只有方舟编译器孤军奋战的!

就算是开发普通的应用软件,我们也要运用编译技术,让它们平台化,让中国制造的软件,具有更高的技术含量,颠覆世界对于“中国软件”的品牌认知。这样的颠覆,在手机、家电等制造业已经发生了,也应该轮到软件业了。

而经验告诉我们,一旦中国的厂商和工程师开始动起来,那么速度会是非常快的。编译技术并没有多么难。我相信,只要短短几年时间,中国软件界就会在这个领域崭露头角!

这就是我们这门课程的目的。不是为了学习而学习,而是为了实战而学习。

当然,课程虽然看似结束了,但也代表着你学习的重新开始。后面我计划再写几篇加餐,会针对C++、Rust等编译器再做一些解析,拓展你的学习地图。并且,针对方舟编译器,我还会进一步跟你分享我的一些研究成果,希望我们可以形成一个持续不断地对编译器进行研究的社群,让学习和研究不断深入下去,不断走向实用。

另外,我还给你准备了一份毕业问卷,题目不多,希望你能在问卷里聊一聊你对这门课的看法。欢迎你点击下面的图片,用1~2分钟时间填写一下,期待你畅所欲言。当然了,如果你对课程内容还有什么问题,也欢迎你在留言区继续提问,我会持续回复你的留言。

我们江湖再见!

精选留言

  • whoami

    2020-11-15 21:58:45

    感受到老师的雄心,看得我嘴唇发干,眼神坚定。坚持学习能够做到,但我有一个坏习惯——贪多,这一块儿不会,学,那一块儿不知道,学,以至于感觉自己处于“学而不思则罔”的状态。焦虑、迷茫、不甘心,老师,你能给我一些建议吗?
    作者回复

    世界是在矛盾中前行的,人是在矛盾中成长的。
    有贪心是好事。否则,连学习动力都没有呢,岂不是更糟糕。
    你遇到的困惑应该不是特例,每个有志青年都多多少少遇到过。

    但是,还是会给你一些具体的建议:
    1.计算机领域的知识确实是很多的,多到一个人一辈子都学不完。你看,前端技术和后端技术;前端分n个平台,m个技术栈;后端也有x种语言,y种架构...。每种学精都要花费大量精力。所以确实要有所取舍。
    2.每个领域的知识都是一个倒金字塔的形状。上层的应用性的技术很多,但底层的基础原理其实并不多。所以,掌握大部分的底层原理反倒是有可能的。
    3.但我们要把底层原理和上层应用性的技术关联起来学习,这样效果会比较好。也就是从上面向下打一个通道。对于你所使用的应用性的技术,你知道从上到下每一层是怎么实现的。
    举个例子,现在很多人都用MySQL数据库。那么从上到下有下面的一些知识要去搞清楚:
    (1)掌握SQL,以及背后的数学基础:关系代数。知道从比较高的抽象度上,其实就是那么几个基本的运算。
    (2)往下一层,了解数据库服务器的架构,知道服务器是如何响应客户端请求的,如何保证事务机制的,在这个过程中是怎么使用日志和锁的。
    (3)再往下一层,探究一下MySQL是如何解析SQL的,如何把SQL分解为第一层中的那些基本运算的。以及它是如何处理并发的,如何处理客户端连接的(比如,如果客户端断连了,服务器的那些运算如何及时停下来),如何生成最佳的SQL执行计划的,等等。这一层次的内容,我在《编译原理实战课》中有所涉猎。
    (4)继续往下,数据是怎样保存在磁盘上的,如何划分磁盘空间的;索引是如何建立和维护的;查找一套数据,都有哪几种方法,代价分别有多高…
    你会发现,如果把MySQL搞清楚,你会涉及到各种基础原理:并发、数据结构(比如索引的结构)、算法(比如最佳执行计划算法)、计算机组成(磁盘IO等)、网络(客户端和服务器的通讯协议)等等各种基础原理。而如果一个同学把这些问题都能搞明白,那么他会成为一个很高级的数据库专家,甚至可以自己动手该数据库中的一些部件,比如实现高效的异地灾备等。
    把自己经常使用的高级语言,从顶到底搞清楚,也会达到同样的效果。我在《编译原理实战课》中也解析了n门语言的实现机制,希望能起到对知识的穿针引线和启发思维的作用。
    4.心态上的调整:要有玩的心态。不要把学习当成任务,不要搞得太功利,要搞得有趣、好玩。我见过很多大牛(包括某些大厂的技术奠基性的人物),都是先玩,搞一些自己喜欢的东东,比如当年某人做了个能在家里默默下载东西的小硬件,还能远程控制,这在当年网速很低的时代很有用,但要做这个小硬件,就需要洞悉网络的基础原理,还要能搞Linux,还要会搞一个小板子。你把这么多知识都能串起来,去搞定某些大型的云计算的场景也是没有问题的(事实也是如此)。
    好了,就说这么多,希望对你和其他同学有所启发!

    2021-01-05 10:00:48

  • VVK

    2020-10-04 12:55:15

    感谢老师的分享,也期待老师对Rust编译器的加餐。
    作者回复

    好!又一个对Rust语言感兴趣的同学!

    2020-11-11 09:47:36

  • 漏网之渔

    2020-10-29 20:04:26

    期待老师对Rust编译器的加餐!
    作者回复

    Rust正在准备中。前几天先把C++的写了一篇。

    2020-11-02 22:03:18

  • andre

    2022-06-14 15:48:33

    宫老师,您有没有编译技术的交流群(微信\qq或者电报都可以),最近在做动态语言的JIT,经常遇到问题苦于无人可以交流,有种闭门造车的感觉。
  • ifelse

    2022-02-01 11:48:51

    学到很多
  • fy

    2021-03-07 17:30:45

    老师,我买的那些课程,还是你这门课程过瘾,编译原理之美也买了,看到了编译器的后端技术,这课程太硬核了,其它课程相比这门课程觉得是小儿科。只有坚持最后,并反复看,才能搞懂。深知你们这一代程序员的功底太扎实了。
  • 无形

    2021-01-11 01:06:20

    加油!一起为中国的软件崛起而奋斗!
    作者回复

    点赞!
    我们可以多做点底层技术了,以前这都是别人说了算。

    2021-01-11 09:56:01

  • 2020-11-12 10:01:55

    感谢老师的分享
    作者回复

    感谢你一直的跟进:-)

    2020-11-13 11:50:06