May 11, 2014 - 编程珠玑番外篇-O 中间语言和虚拟机漫谈

Comments

本文原发于《程序员》2014年3月刊

导言

编程语言的发展历史,总的来说,是一个从抽象机器操作逐步进化为抽象人的思维的过程。机器操作和人的思维如一枚硬币的两面,而语言编译器就像是个双面胶,将这两面粘在一起,保证编程语言源程序和机器代码在行为上等价。当然,人本身并不是一个完美的编译器,不能无错的将思维表达为高级语言程序,这种偏差,即Bug。因为编译器的帮助,我们可以脱离机器细节,只关心表达思维和程序行为这一面。

编程语言的发展日新月异。特别是随着对问题的深入理解,新的设计思想,语法构建和新的领域相关语言(DSL)层出不穷。而硬币的另一面似乎一直波澜不惊。这是自然的——无需关心底层架构的变化,或者目标代码生成优化等技术的进化,正是编译器带给我们的好处,因为这些细节和要解决的问题往往关系不大。

尽管所受的关注度不高,这些底层的技术一直在持续地进步。特别是这十年来,一场大的变革正在悄悄发生。这场变革,就是中间语言和虚拟机几乎成为了编程语言的标配——编译器不再以机器的CPU指令集作为编译目标,而是生成针对某种中间语言或虚拟机指令集的目标代码。这场变化是深刻的,它意味着编程语言的设计者自此完全脱离了具体硬件平台的束缚,语言如何设计和如何执行成为了两个完全正交的系统。这个变革大幅度降低了创造一个新语言的成本,一下子把我们推入了一个语言井喷的时代。

从抽象语法树到中间语言

熟悉编译器设计的读者都知道,编译的第一步是构建一个叫抽象语法树(AST)的数据结构 (脚注: 语法树这个概念来源于 LISP)。有了这样的数据结构后,解释器和编译器在此分野。以 AST为起点,解释器完全可以遍历语法树,递归执行每个子结点。IEEE POSIX (或称标准UNIX) 规定的 AWK 语言,其经典实现就是一个生成和遍历语法树的过程:

syminit();

compile_time = 1;

Node *winner ; /* root of parse tree */

yyparse(); /* generate parse tree */

if (errorflag == 0) {

compile_time = 0; /* switch to execution */

run(winner); /* execution of parse tree starts here */

}

Awk 这样的传统解释器的优点在于结构简单,开发便利。事实上许多领域专用语言都采取这种方式实现,如 PostScript, Matlab, R 等。

解释执行的缺点也是显而易见的。首要的一点就是每次执行都需要重新生成语法树。领域专用语言或许可以忍受每次零点几秒的重复解释过程,而对于可以开发大型应用的通用编程语言来说,这一点是致命的。每次重新生成语法树也意味着这样的语言难以用于资源受限系统,因为

语言本身语法结构复杂,布置一个解释模块的代价往往非常高昂。为了避免解释执行的这些弊端,传统的编译器致力于只解释一次,将通用语言的语法树,直接转变为目标机器的 CPU 指令。传统的 FORTRAN 和 C 编译器就是如此设计的。有些编程构建,如 C 语言中的 i++, 甚至是直接受 CPU 指令影响的产物。

上个世纪 80 年代后期,随着对程序效率优化和 LISP 机器的研究,研究者们认识到,其实传统的编译和解释并不是对立的概念。特别的,编程语言的语法树转变可以为一种中间指令格式。这种中间指令格式贴近机器指令,可以进行运行效率优化。传统的以生成目标指令的编译器,可以将中间语言简单转为机器指令。而解释器,也省却了多次的语法树生成,而直接解释相对简单的中间语言。

较早在中间语言上进行探索的是 MIT 的 LISP 机器。如 Thomas Knight,他的研究集中在如何在硬件上实现一个高效的 LISP 环境。显然,没有一个硅片可以直接运行 mapcar,但设计一个支持 mapcar 的中间语言并不困难,只需要支持一些基本的列表操作即可。这种设计思想影响了很多后来的系统。流行的 GCC 编译器,从结构上来说分前端和代码生成端两部分。连接两者的中间语言 RTL 的基本一些指令,都可以追溯到 LIPS 机器的指令集。

中间语言和虚拟机

中间语言可用于程序优化的原因是显而易见的:这种中间格式既贴近机器代码,又保存了原有程序的结构。程序优化并不是一门魔术。像循环展开,死代码消除等技术,都依赖于程序控制结构,而中间语言可以保持这样的控制结构。事实上,目前我们所知的编译优化技术,无一不是建立在结构分析之上。中间语言的出现让程序优化成为了一个独立的问题。原本单列的 C 程序优化, FORTRAN 程序优化如今统一归结为 RTL 程序优化。编译器前端可以千差万别支持许多语言,但负责优化和翻译为目标代码的后端均归为一个,就此一点,就大大简化了语言编译器的设计门槛。现如今,几乎没有一个语言设计者需要考虑如何生成高效目标代码了。

当然,中间语言的作用并不仅限于目标代码优化。如果我们把中间语言也当作一种语言的话,不难发现中间语言甚至比原语言更加普及。 比如,Java 虚拟机(JVM) 语言实际上是一个比 Java 语言成功许多倍的产品。JVM 存在于众多Java 语言不存在的地方。像 Jython, Scala 和 JRuby 这样的语言,均依赖于 JVM, 而非 Java 语言本身。

语言的虚拟机的本质,是一个可以运行中间语言的机器。 在实际硬件上,程序和数据是两个截然不同的概念;而对于虚拟机来讲,中间语言程序,只是虚拟机程序的输入数据罢了。这种将程序当作数据的处理方式,带来了我们熟知的许多虚拟机的优点,如跨平台特性,安全性等等。 因为程序即是数据,为虚拟机读取中间语言程序方便,其指令往往都是以字节为单位,故称为字节码 (bytecode)。 相比之下,计算机的 CPU 指令则可长度不一,也不一定占据整数个字节。

程序是数据这个特性使得虚拟机可以做到跨平台和沙箱安全;反过来,数据是程序又使得虚拟机可以用在一些意想不到的地方,使数据更加灵活。 目前通行的轮廓字体描述语言 TrueType 就是成功运用虚拟机来更加灵活地处理字体的一个例子。

TrueType 是一种采用数学函数描述字体的矢量字体。 矢量字体在理论上可以自由缩放。而实践中,因为显示器本质上是点阵的,所有的矢量字形都要经过栅格化 (rasterization) , 将矢量轮廓近似转化为像素点的透明度。 然而,这种近似并不是随意的。 以汉字 “中” 为例,为保证其对称美观,我们必须约束栅格化程序,保证任何时候左右两个竖线与中间一竖的距离相等,哪怕为此不惜将此字缩减或放宽一两个像素。 这类约束又被称作提示 (hinting)。 它对于字体至关重要—缺少提示的矢量字体在字形较小时不可避免地会出现失真,变形和锯齿等现象。 不难理解,本质上“提示”是一个以字体轮廓和字形大小为输入,以栅格数据为输出的程序。 因为此,TrueType 包含了一套虚拟机指令,方便字体设计者表达这种提示。 可以想象,如果没有这个虚拟机的存在,设计灵活的矢量字体是不可能完成的任务。 实际上,我们所见到的几乎所有的矢量字体文件,都是一个数据和程序的混合物。 从另一方面来说,每个字形都需要一个专门的“提示”,也从一个侧面说明了设计高质量的中文字体之难度。

基于栈,还是基于寄存器

凡提到虚拟机,绕不过去的第一个问题就是这个虚拟机是基于栈的,还是基于寄存器的(有些虚拟机,如 LISP 机器,可以同时有栈和寄存器)? 尽管这里“寄存器”和“栈”,都不一定直接对应到机器CPU的寄存器或者内存里的栈。这个问题之所以重要,因为它直接决定了虚拟机的应用场景。一般说来,基于栈的虚拟机结构相对简单,且更加适合资源受限系统。 比如上文我们说的 TrueType 虚拟机,结构简单,功能专一,就是基于栈的。

尽管所有的计算机的存储模型都是构建在图灵机的无穷纸带模型上,实践中所有语言都或多或少依赖于栈模型。特别的,函数调用就等价于栈的推入和弹入操作,其他操作均可抽象为对栈顶元素进行。相比之下,寄存器模型虽然贴近真实机器,却并不够直接:很少有高级语言直接制定寄存器如何分配的,因此编译器的作者需要考量寄存器分配问题。而基于栈的虚拟机的所有指令都可默认为对栈顶元素操作,结构简单,且暂时绕开了寄存器分配难题。

基于栈的虚拟机更加适合内存和 CPU 处理速度等方面有限的系统。同样的源程序,在目标代码的体积上,面向栈虚拟机上生成的代码更加小。这是容易理解的:基于栈的虚拟机的指令默认对栈顶元素操作,因此指令只需为 OP 格式,无需 OP Reg1, Reg2, Reg3 等额外指定寄存器。这个设计也绕开了指令解码问题。平均上说,基于寄存器的虚拟机生成的指令的体积比基于栈的要大。我们见到的许多基于栈的虚拟机,都是为资源受限系统设计的。JVM 的初衷是一个运行在电视机顶盒中的小系统,后来精简版本的 JVM 甚至可以放到智能卡上;Forth 语言的虚拟机是要用在计算机固件(Open Firmware),航空系统和嵌入式系统中;控制打印的 Postscript 是用于高品质打印机中。很显然,机顶盒,引导固件和打印机都是资源受限的系统,这些系统中的虚拟机,不约而同都是基于栈的。值得一提的是,因为实现简单,许多并非用于受限系统的通用语言的虚拟机也是基于栈的,如 Python, Ruby, .NET 的 CLR 等。

基于寄存器的虚拟机,是为性能所生。引入寄存器假设固然关上了用于资源受限系统的门,却也打开了一扇通向进一步性能优化的窗。栈虚拟机的一大缺点就是要不停地将操作数在堆和栈之间来回拷贝。比方说一个简单的三个参数的函数调用,在传递参数上就需要至少三次入栈和出栈操作,而在寄存器上只要指定三个寄存器即可。现代处理器提供的通用寄存器支持,本身就是为了减少这类值的来回拷贝。尽管有 Hotspot 这样的技术能够将一段栈虚拟机指令转化为基于寄存器的机器指令,可毕竟没有直接从支持寄存器的中间语言翻译直接。前面说过,保持程序的结构是优化的先决条件。失去了“指定三个值”这样的结构的栈虚拟机,需要运行时间接的推断这个操作。而直接指定这些访问结构,将值直接映射到 CPU 的寄存器,正是这类虚拟机运行效率高的要点所在。Android 的 Dalvik, Perl 的 Parrot 都是基于寄存器的虚拟机,而 LLVM 则是基于寄存器假设的中间语言。其中,为了让 Android 程序更加快的运行,Google 不惜放弃 JVM 的指令集,而选择将 JVM 指令转化为基于有限个寄存器的 Dalvik 指令集。 Parrot 和 LLVM 则更加自由一些,假设了无穷多个寄存器。无论是有限还是无限个寄存器,省却不必要的值拷贝是这类中间语言的最大优点。

JIT 和直接执行

JIT (Just-in-time) 是运行时的动态编译技术。不难看出,JIT 是针对中间语言的——将原语言的编译推迟到运行时并无意义,将中间语言的解释,部分转化为编译后的机器代码,则可以优化运行效率。JIT 之所以可行,一个基本假设是程序大多存在热点。D. E. Knuth 三十年前观察到的一个现象: 一段 FORTRAN 程序中不到 4% 的部分往往占用超过 50%的运行时间。因此,在运行时识别这样的热点并优化,可以事半功倍地提高执行效率。

按照 Jython 作者 Jim Hugunin 的观测, JIT 技术出现后,同样功能的程序,运行于 Java 虚拟机上的字节码和直接编译成二进制代码的 C 程序几乎一样快,有的甚至比 C 快。乍一看虚拟机比原生代码快,理论上是不可能的。而实践中,因为 JIT 编译器可以识别运行时热点做出特别优化。相比之下,静态编译器的代码优化并不能完全推断出运行时热点。而且,有些优化技术,如将虚函数调用静态化,只有在运行时才能做到。在对热点深度优化的情况下,JIT 比直接生成的机器代码执行效率高并不是一件神奇的事情。引入了 JIT 的,以 Python 书写的 Python 执行器 pypy, 运行速度要比以 C 实现的 CPython 解释器快一到五倍,就是 JIT 技术魅力的一个明证。

尽管 JIT 技术看上去很炫,实践中也能够做到几乎和原生二进制代码速度相近,我们必须承认,这只是一种补救相对慢的中间语言解释的一种措施罢了。设计语言平台时,设计者可能因为这样那样的原因而选择中间语言/虚拟机解决方案,或因为针对嵌入式系统(Java),或因为跨平台要求(Android Dalvik),或者仅仅因为设计者想偷懒不愿写一个从语言到CPU指令的编译器(Python/Ruby)。无论原因为何,当最初的原因已经不存在或不重要,而性能又成为重要考量的话,采用中间语言就显得舍近求远。JavaScript 引擎的进化就是一个生动的例子。

JavaScript 语言最初只是一种协助 HTML 完成动态客户端内容的小语言。Netscape 浏览器中的JS 引擎,最初只是一个简单的解释器。自2004 年 Google 发布 Gmail 之后, Ajax 技术的发展对 JS 引擎的速度提出了更高的挑战。JavaScript 引擎的速度被当成一个浏览器是否领先于对手的关键指标。在此情况下,众多浏览器厂商纷纷卷入了一轮 JS 引擎速度的军备竞赛。

最先挑起这场战争的是 Firefox, 目标是当时占据90%市场的 IE。Firefox 3 于2008年6月登场,其 JS 引擎 TraceMonkey 在栈虚拟机的基础上首次采用了 JIT 技术,在当时众多标准评测中超越了IE7。就在当月,WebKit 开发小组宣布了基于寄存器的 Squirrelfish 引擎,殊途同归,也是基于中间语言,尽管两者互相不兼容。

到9月,Google 发布了第一个版本的 Chrome 浏览器以及新的 JS 引擎: V8。V8一反使用中间语言的设计套路,力求将 JS 直接编译到本地代码。Google 毫不掩饰 V8 在标准评测上比其他浏览器快的结果,因此造成了 Firefox 和Safari 开发者对各自 JS 引擎速度评测的一场恶战。到了9月的时候,Firefox 和 Safari 各自的引擎都比6月份的结果快到 20%到60% 不等。 而 V8 也赢得了许多眼球,催生了之后的 Node.js 项目。

这场军备竞赛的一个结果,就是 V8 以外的引擎,也开始探索绕过中间语言从 JavaScript 直接生成二进制的可能性。SquirrelFish Extreme 就是自 Squirrelfish 衍生出来的一步本地代码的引擎。值得注意的是,尽管都是生成本地代码,V8 和 SquirrelFish Extreme 这样的编译器,并不是退回到传统的编译器技术上,因为他们已经吸收了许多对 JIT 编译器性能的研究成果。

就在我写这篇文章的时候,Google 正在将 Android 执行环境,从原来的 Dalvik 虚拟机,换成可以直接生成机器代码的 ART 架构。ART 负责在 App 安装后一次将跨平台的字节码分发格式,编译成原生机器代码。20 多年前,为了跨平台,Java 采取了虚拟机的设计方案。如今,中间语言的跨平台的部分依然保留,但作为已经不直接参与执行了。硬件的进步带来的中间语言和虚拟机设计的进化,是当时的设计者如何也想不到的事情了。

May 29, 2013 - 现代人的必备知识:药理学

Comments

人们常说,是药三分毒。我们感性地知道药物是有毒性的,但轮到生病的时候,多数人还是会寻求药物治疗,而把“是药三分毒”放在了一边,或者“相信”药物的效用超过了毒性。这种相信,大多数时候并不是出于我们的理性的判断,而是依赖于医生的判断,以往的经验,或者药物本身的说明书。

 

其实,作为一个现代人,我们完全有能力,也负有对自己和自己爱的人的责任,去了解药物的具体毒性,以理性地,正确地选择药物,从而走出感性地“是药三分毒”的认识,切实地了解药物的毒性。研究药物作用机理和毒性的知识的学科,叫做药理学(Pharmacology)。

 

药理学关心的是药物的结构,作用机理,代谢方式,毒性等等的知识。这些知识看上去名词一大堆,牵涉到化学,分子生物等若干领域,其实只要掌握了一些基本的概念,一个受过高中教育的人完全可以理解。事实上,在美国,药理学是护士培训的必修课。护士并不比大多数读者具有更多的生物,化学或者科学知识。

 

以一个很日常的例子说明一下药理学的作用。几乎所有人都用过的退烧药。常用的退烧药包含阿斯匹林 (Aspirin),布洛芬 (Ibuprofen),扑热息痛 (Paracetamol,国内又叫百服宁,必理通或者泰诺) 三种。现在请听两道题题:1)这三种药一天一次还是一天多次,如果多次,间隔多长为好?2)如果你同时有胃溃疡,或者肝功能不好,该选择哪种药物?读者要说了,我又不是医生,我哪儿知道?其实,如果你有一些基本的药理学知识,回答以上问题轻而易举。

 

药一天吃几次的问题,其实就是人体多长时间将药代谢掉的问题。在药理学里,我们用“半衰期(half life)”来衡量药物的代谢速度。如果你到英文维基百科上,分别输入 Asprin, Ibuprofen 和 Paracetamol,在右边的框里,很容易看到这三个药物的半衰期(300mg, 3.1-3.2小时,1.8-2小时和1-4小时)。人体对药物的代谢是非线性的,但从半衰期我们可以大致估计到在症状持续的情况下,这些药不可能在体内持续作用 24 小时。所以如果症状持续,我们可以一日多次服用。像扑热息痛,因为半衰期已经长达 4 小时,所以服药间隔最好要在 4 小时之上,这都是有药理学知识的人一眼看出的常识。再比如说,同样是抗生素,两颗阿红霉素(Azithromycin) 的半衰期长达68小时,而阿莫西林(Amoxicillin) 只有一个小时。所以你去药房拿药的时候,药剂师会给你几颗阿红霉素,或者一盒子阿莫西林。掌握药理学常识可以帮助我们理解这些药物之间的差别,并记得按时服药。

 

药理学还研究一个药物的代谢途径,比如,是通过肝脏,肾脏还是其他,具体的代谢通道是什么。有了一些基本的药理学知识,我们就知道如果一个人肝功能有损伤,则要避免通过肝代谢的药物。家里养猫的人可能都知道,Paracetamol 对猫有剧毒,原因就是 Paracetamol 是肝代谢的,而猫的肝恰恰没有代谢 Paracetamol 所需要的酶。又比如说,阿司匹林 和布洛芬在药理学上都属于NSAID 药物。NSAID 药物的一大副作用是能够导致消化道溃疡,所以有消化道溃疡前史的患者最好谨慎服用。总之,虽然这些药都是属于“退烧药”,通过药理学,我们可以详细探究这些药的异同。如果我们知道了一个药的代谢渠道,在通过简单的基因测序如 23andme 知道我们自身有无相应的酶来代谢这些药物,在选择药物的时候,就不会像猫一样盲目地把 Paracetamol 往嘴里送,也不会因为无知而莫名其妙地把所爱的人推向危险的境地。

 

言而总之,药理学知识可以帮助我们了解药物。现代药的说明书说白了是很严谨的药理学试验报告,而药理学知识就是读懂这个报告的钥匙。我相信药理学可以帮助我们更加科学和透明地选择和服用药物,让我们和我们所爱的人的生活更加美好。

 

这个世界上有很多所谓的“传统医学”,而且信徒还不少。不管这些传统医学多么博大精深,多么神奇,如果这些传统医学给我或者我所爱的人开的药没有药理学支持,那么我们作为一个现代的,理性的人,唯一可做的,而且是最负责的,就是拒绝这些药物。把这些药物往嘴里送,就是接受了“是药三分毒”,而且还不知道是哪三分。

PS: Coursera 上有  Fundamentals of Pharmacology 的公开课。我上过,受益无穷。

Mar 3, 2013 - 编程珠玑番外篇之番外篇-N 答 UNIX 痛恨者王垠

Comments

(标题是标题党)

王垠最近的一篇文章中,提出了很多有趣的观点。其中最核心的一点,就是 *NIX 系统的设计哲学非常糟糕,而 Windows 系统才是真正为开发者设计的系统。凡是涉及到哲学层面的争论,最后都是以谁也说服不了谁收场。我相信王垠有足够的理由来证明 UNIX 设计哲学的糟糕,但遗憾的是,他的文章并没有表现出这一点。我摘抄一些论点并作答复。

Unix 的 shell,命令,配置方式,图形界面,都是非常糟糕的。每一个新版本的 Ubuntu 都会在图形界面的设计上出现新的错误,让你感觉历史怎么会倒退。但是这只是表面现象。Linux 的图形界面(X window)在本质上几乎是不可治愈的恶疾。

以现在的眼光看,X Windows 是一个设计过于繁复的系统。实际上,20年前出版的 The Unix Haters Handbook 里,就有专门的一章论述为什么 X Window 的 client-server 架构是糟糕的。可是,这和 Ubuntu 的设计演化之间似乎没有太多关联。Ubuntu 所谓的图形界面设计的错误(以 unity 为例),都是桌面环境层面的问题。一个设计师在这个层面犯错误,就像一个画家因为构图不够工整就去怪画布不行一样,之间还相差不少逻辑链条。

X Window 和其他操作系统上的 GUI 系统最大的不同,是它和宿主操作系统的松耦合。因为这种松耦合的存在,在不需要图形界面的地方,操作系统可以不带 X Window。很多云服务的服务器,都是没有 X 的。当下如日中天的移动操作系统如 Android 和 iOS 都是 UNIX 家族操作系统,而这两者都没有用 X Window 提供 GUI 支撑,而是另外开发了一套专门适合触摸式移动设备的图形界面系统。图形系统和操作系统间的松耦合,使得操作系统可以从头搭建适合具体设备的图形界面交互(如 Cocoa Touch),并且快速的迭代(如 Project Butter)。

在 GUI 和内核的耦合关系上,架构的确决定了产品的形状。我们都知道,微软 NT 内核和 Widnows UI 系统是绑定在一起的。Windows Phone 8 要和 Windows 8 共享内核和其他组件的结果,就是它们都必须兼顾桌面和移动平台。为此,微软做出了两个可以做榔头也能做螺丝刀的东西。一个是 Windows 8,支持触摸屏,Modern UI 界面长得像手机界面;一个是 Windows Phone 8,界面很适合触摸设备,却又同时支持移植来的桌面程序,造成有的程序界面长得像桌面。无论你认为哪个操作系统的图形界面漂亮,哪条路更加有光明的未来,微软的这些系统之间的关系之繁复,开发迭代的周期如此之长,都是客观事实。

UNIX 系统的Unix 的 shell,命令,配置方式的确有不少的问题,在痛恨者手册里也有详细的论述,我以前也写过,就不一一列举了。

Unix 依靠自己的“宗教”和“哲学”,“战胜”了别的系统在设计上的先进,统治了程序员的世界。胜者为王,可是 Unix 其实是一个暴君,它不允许你批评它的错误。它利用其它程序员的舆论压力,让每一个系统设计上的错误,都被说成是用户自己的失误。其它系统里面某些优秀的系统设计,也许就要被历史掩埋……

一个操作系统,是不可能凭着“宗教”和“哲学”就能统治程序员的世界的。程序员不是天主教徒,UNIX 也不是程序员世界的教皇。实际上,所谓的 UNIX 系统,不是一个特定的系统,而是一个家族的系统。这个家族的系统包罗万象。不喜欢微内核的做了宏内核,不喜欢一切还不都是文件的做了 Plan 9, 不喜欢 X Window 慢如蜗牛的做了 XGL 加速。优秀的设计不断地加入这个系统,改造这个系统。UNIX 来源于 Bell 实验室,X Window 却是 MIT 的,BSD 来自于 Berkeley, Solaris 来自于 SUN, Mac OS 来自于 Apple。如果说这里面有宗教的话,这一定是世界上最诡异的宗教,里面的教徒还天天打架。

在 UNIX 系统中,所有的设计,都在开放的环境下竞争。我们可以说 UNIX 不是一个设计良好的系统,但是它的设计哲学在竞争中获胜的原因,不是因为它是“暴君”,控制了程序员的思想,而恰恰是因为它的开放,所以最终汇总了很多优秀的东西。至于 UNIX 这种不怎么好的系统为何最终获胜,20年前的一篇文章也讲得很清楚了。

因为 TeX 的语言是非常糟糕的设计。它的设计者几乎完全不明白程序语言设计的基本原则,不明白什么叫做“抽象”。

而这些源于 Unix 的工具却像是“魔鬼棋”或者“三国杀”,有太多的,无聊的,人造的规则。有些人鄙视图形界面,鄙视 IDE,鄙视含有垃圾回收的语言(比如 Java),鄙视一切“容易”的东西。他们却不知道,把自己沉浸在别人设计的繁复的规则中,是始终无法成为大师的。就像一个人,他有能力学会各种“魔鬼棋”的规则,却始终无法达到象棋大师的高度。

这里,王垠把两个不相关的东西放到了一起。一个是工具的设计哲学,一个是我们如何学习知识。魔鬼棋本身是一种工具设计哲学,和成为大师无关。

军刀工具一文中我提过,面向特定领域的软件工具之所以让人觉得复杂,是因为这个问题本身复杂。我们把解决特定领域问题而所需的知识叫做”领域模型“(domain model)。如果我们不了解领域模型,就不能理解为什么 Photoshop 比系统自带的 Paint 复杂几千倍, 或者为什么我们需要正则表达式这种诡异的东西。我们讲的复杂与简单,都是工具设计哲学层面的。

以王垠说的 TeX 为例。写出《计算机程序设计艺术》的 Knuth 到底知不知道程序语言设计的基本原则我们可以不加讨论。了解一点字体设计和排版的都知道,计算机排版问题是个复杂的问题。的确,软件工具的设计目标,是把复杂的问题简化。然而,大多数人不知道的是,简化问题是一个两步过程。第一步,我们需要把现实的问题映射到一个领域模型。第二步,是把这个模型简化到我们人可以处理的地步。很多时候这两步合并起来了,让我们觉得这两步好像是一步,并且认为所有的设计,都应该朝简化的方向走。这是一个对设计的错误认识。

举个非计算机领域的例子:用电饭锅煮饭非常简单,加米加水再按个按钮就行了。电饭锅的设计者的设计目标是操作简单且能完美地煮米。作为工具的设计者,它一方面需要了解大米是怎么煮熟的,另一方面需要提供给用户一个简单的按钮。TeX 作者,从一开始就不是设计一个电饭锅,而是一个精确的温控炉子。有了这个精确的温控炉子,想烧饭的可以把它封装成电饭锅,想做蛋糕的可以把它封装成蛋糕烤箱。设计电饭锅的人的设计,并不比设计精确的温控炉子的人好,或者差。设计者的初衷决定了产品的形状。 Kunth 的初衷,正是设计一个可以让他人排版出任何想排版的东西的系统。也就是说,做出一个最终非常简单的,只有一个按钮的排版系统不是他的设计目标。做出一个可以高度定制的系统才是他的目标。

其实,TeX 本身也是一个由繁到简的软件系统。它把所有排版中的问题,都提炼成了一些控制原语。有了这些控制原语,针对特定领域做优化就不是问题了。现在通用的科学出版排版工具 LaTeX, 正是这样的一种优化。这是有心设计的结果。我们抱怨 TeX 复杂,其实是抱怨排版本身复杂。Windows 系统上有许多排版软件,可以毫不客气地说,没有一个可以达到 TeX 所能到达的精确控制。以此责怪 UNIX 下的软件工具是魔鬼棋,就类似于责怪 Photoshop 为啥不象 Paint 那样简单一样,有选择性地忽视了两者所要解决地问题不一样。

我欢迎所有的 UNIX 使用者加入痛恨者阵营,因为我也是 UNIX 痛恨者。只有成为了 UNIX 痛恨者,你才是一个真正的 UNIX 使用者。至于 Windows, 我们对它没有感情,无所谓爱恨。

Oct 24, 2012 - 佛教艺术中的沙

Comments

  • “沙” 是佛教里很值得一提的物件,很多佛教艺术都和沙有关。

日本禅宗的“枯山水”就是一种依赖于沙的园林艺术。枯山水在西方很有名,以至于在西方直接被称为禅花园(Zen Garden)。一般的园林都会通过假山和水构景,通过人造的山和水,来引起观者自然的联想,从而联系人与自然。而枯山水园林却反其道而行,不采用真正的山,也不摆放真实的树,竹等物。枯山水中的水,是细细耙制的静态的白色砂石,模拟水的动态形态;枯山水中的植物也极少或者没有。这种美学上的差异,其实是和中日两国禅宗在修行上的侧重不同有关的。

中国禅宗传统上就是位于名山大川,因此在修行时秉承了一种自然主义的路线,让修行者在见山是山,见水是水的自然景观或者园林中修行。日本的禅宗主要继承了曹洞和临济两家。曹洞侧重于打坐和冥想,即“只管打坐”(Shikantaza)。临济强调”心即是佛”,也是促使人向内求索。在这种思想指导下,园林就不再坚持自然主义,而转为为修行着提供安心之法。枯山水正是这种哲学的体现。一个枯山水园林一旦建成,一年四季景色完全相同,恒久不变,宁静无碍。在枯山水中修行,心不会四处乱走。

枯山水是用静态景观来展现这个大千世界动态的一瞬间,而藏传佛教里的坛城沙画 (Sand Mandala),则是用动态的构建,来展现万事无常这个恒久不变的佛理。构建坛城沙画的目的,不是为了让它成为一项艺术品,而是为了最后摧毁它。这种最终什么也得不到的艺术创作,正是说明了佛教里一切无常的道理。

佛教的艺术家很早就意识到沙是彩色的。《大方广佛华严经》就提到过四条不同的大河流出不同颜色的细砂:“恒伽河口流出银沙,私陀河口流出金刚沙,信度河口流出金沙,缚刍河口流出琉璃沙”。结合华严经所说的恒河沙中的世界,用彩色的沙构建大千世界这种佛教艺术的出现是自然的。

为了构建坛城沙画,喇嘛们需要用尺子和圆规仔细的规划沙画的基本结构和几何形态。同时,所有的彩色沙都是仔细遴选甚至磨制而成。这些彩色的细沙被装入一个小口沙漏,然后由有经验的喇嘛,将这些细沙细细铺出。构建沙画是费时费力的,往往需要众多喇嘛们连续好天的工作。而不管花多少力气,多么美丽的沙画,只要沙盘倾覆,都一瞬间化为无形。从这个意义上来说,要揭示世事无常的道理,没有沙更加贴切的道具了。

坛城沙画

坛城沙画一旦建立,它的唯一命运就是被摧毁。高级别的喇嘛会先诵经一段,然后以一定的规则将沙画摧毁,将各色细沙汇作一堆,装入瓶中,撒入江河。就像熵只能增加一样,美丽的沙画的每一次破坏都是不可逆的。这种摧毁美丽艺术品的悲剧感和无力感,正是佛教想要说明的道理:诸行无常,是生灭法,生灭灭已,寂灭为乐。

佛经里对沙的数量,沙的色彩和沙的无常感都有论述。有趣的是,尽管大多数人认为“一花一世界,一沙一凡尘”出自于佛经,实际上佛经中从来没有明确的提出过这个论题。这个很有佛学意味的句子,实际上来自于英国诗人_William Blake_ 的《纯真预言》:

To see a world in a grain of sand,
And a heaven in a wild flower,
Hold infinity in the palm of your hand,
And eternity in an hour.</p>

May 21, 2012 - SpaceX 开创了人类航天的新纪元

Comments

今天,美国一家私人公司 SpaceX 成功的从美国佛罗里达州肯尼迪航天中心火箭的空军发射场发射了一颗火箭,这颗火箭的终点,是国际空间站(ISS)。这是继上次成功发射并回收卫星后,一家私人公司首次将载荷送到国际空间站附近。自美国的航天飞机退役后,能够将载荷运输到国际空间站的方法只有一个–俄罗斯的联盟号飞船。而现在,私人航空已经离这个目标不远了。

SpaceX 的成功是美国社会一直引以为豪的企业家精神和创新精神的成功。2002 年,eBay 以 15 亿美元的价格购买了 PayPal,一家从事电子支付的公司。很多 PayPal 的员工一夜之间成为亿万富翁、PayPal 的共同创始人 Elon Musk 极具冒险精神,眼看 PayPal 已近逐渐成熟,便在 2002 年 eBay 收购还没定型之前,马不停蹄的创立了一家叫做 SpaceX 的公司,致力于民用航天探索。

和 Jobs 用自己的钱投资 NeXT 和 Pixar 一样,Musk 将自己口袋里的 1亿美元投给了 SpaceX。当时,Musk 同时主持两家创业公司,一家是 SpaceX,从事空间探索,另一家是 TelsaMotor,从事电动汽车生产。这两家公司都不是传统的软件或互联网公司,因为空间探索和生产汽车的成本,无需多说,公司的烧钱的速度远远快于硅谷的其他创业公司。可以想象,这两家公司在发展过程中屡屡不绝的资金不足的问题。最后问题严重到需要 Musk 将自己口袋里仅存的 70 万美元拿出来发工资的地步,这在投资人遍地的硅谷是非常少见的事情。

如果换到其他创业者,到了这种地步,可能唯一的路就是缴械投降了。和 Steve Jobs 一样, Musk 的过人之处之一,就在他不但毫不畏惧风险,而且还能说服别人和他一起冒险。通过与 PayPal 一帮一夜暴富的富翁之间的关系,SpaceX 从一家创业基金拿到了一笔钱,暂时保证了公司不会关门大吉。同时,他加快了说服军方和NASA与 SpaceX 签订合同的谈判。2005 年,在 SpaceX 最紧要的关头,美国空军与 SpaceX 签下了价值一亿美元的发射合同。随后,2006年,NASA 与 SpaceX 又签订了价值10亿美元的向国际空间站运送补给的合同。这些合同带来的巨额项目投资,使SpaceX 终于摆脱了市场和资金的两大问题,成为民用航天的一家标杆,此后就是大家熟知的火箭发射,卫星回收等历史了。而 Musk 在 TeslaMotor 一侧的成功也非常耀眼。该公司是第一家量产锂电池汽车的公司,并在 2010 年成功上市。就像当年 Jobs 同时运营两家公司最后让 Pixar 上市一样。

SpaceX 用很少的钱,在极短的时间里完成了某些号称体制优越的国家举国都不能完成的事情(我说朝鲜呢,不许联想),最重要的当然是 Musk 的远见,无数的工程师杰出贡献。这里,美国政府和 NASA, 也起到了锦上添花的作用。美国政府不敢擅自夸耀自己,但公平的说,SpaceX 的成功离不开美国的航天政策调整。

美国政府在空间探索上的策略虽然一个总统一变,但因为 NASA 具有较大自主性,因此空间政策大体上还是很有政策的延续性的。因为航天飞机的退役日程,小布什政府一直在规划如何在航天飞机退役后继续维护国际空间站。当时小布什政府的设想是一箭双雕,既要国际空间站,又要重返月球。这样,NASA 就提交了一个双火箭的星座计划。因为该计划预算巨大,而美国政府又面对巨大的预算压力,因此联邦政府要求 NASA 同时也寻找其他替代方案,而 SpaceX 的火箭和龙舱,即在当时被  NASA 选中。

奥巴马政府的空间政策比小布什政府更有远见。因为预算问题,奥巴马政府取消了星座计划的登月部分。NASA 内部的一些专家早就预言过,现在已经不是冷战时期,美国公众根本不想和中国比赛登月。如果美国想再次登月,就要消减国际空间站。而且即使美国登月,中国不会因为美国再次登月了就放弃登月,所以美国和中国竞赛登月连政治意义都没有,美国就应该放弃登月。奥巴马政府采纳了这样的建议,让NASA跳过月球,除国际空间站外,把眼光放在 James Webb 太空望远镜等太阳系尺度的空间探索上。因为这样的政策调整,NASA 得以缓出人员和精力从事更加深度的空间探索,而将给国际空间站运输货物这样“技术含量较低”的项目,交给 SpaceX 这样的私人公司完成。NASA 和美国政府都不顾及所谓的面子问题,在航天飞机退役后,委托俄罗斯用联盟号飞船运送美国宇航员和货物。从短时间来看美国好像丢了人,而长时间来看,是省了NASA的预算,并给了 SpaceX 等私人航天巨大的成长空间(美国私人航天领域公司很多,不限于 SpaceX,SpaceX 是领跑者)。

按照 NASA 的计划,SpaceX 需要先将龙舱发射到国际空间站附近,伴随飞行。然后下一步设计出能够于国际空间站对接的龙舱,最终完成飞行,对接,分离的三步目标。随着 SpaceX 的成功发射,未来已经离我们不远,航天历史正在翻开新的一页。