ARTICLE · 1317085
从软件到硬件
软件和硬件之间,没有严格的界限。
芯片只是一段代码的另一种形式。
芯片的效率取决于它的代码的效率。
代码的效率取决于它的表达的效率。
代码表达的效率取决于程序员对问题领域本质的理解程度。
一个领域真正的专家可以用一句话把公众讲清楚,一个初学者可能需要费很多口舌,也无法让公众真正明白该领域的本质。这,就是语言表达效率的区别。
说出了真相的语言,是最强大的语言。
一句话表达的效率,取决于其说出真相的程度。
一个领域真正的专家之所以能够一句话将外行讲清楚,是因为他已经看透了该领域的真相,他的表达最接近该领域的真相。
机器语言和自然语言没有本质区别,都是表达的工具。自然语言是面向人类的表达,而机器语言是面向机器的表达。
将一段效率低下的程序用最尖端的3nm技术蚀刻成芯片,这颗芯片的效率依然很低下。
芯片真正的瓶颈,不在硬件,而是在软件,在表达,在哲学,在求真。
“我们真正的问题,在于对编程概念和语言架构的底层哲学关系缺乏深入理解和正确的认识。”
———— Guy Lewis Steele Jr., MIT
案例
下面,我们通过一个例子来说明。
人类是创造者。任何事物的创造都是经由以下三个步骤:
1. 萌发于意识
2. 宣告于语言
3. 落实于现实
当第三个步骤完成,事物就经由意识投射到现实,展开成时空。
一个强大的程序架构,也是最接近真实世界的现实的架构。因此,想要突破芯片的瓶颈,我们的代码也需要反映出上述事实。
人工智能的本质,是研究人类为了解决问题而操纵符号的能力。
符号的操纵是个过程,这个过程需要创建和删除数据(函数即数据,数据即函数),这就是空间的表达。
这个过程还需要表达操纵的先后顺序,这就是时间的表达。
来看一个最简单的例子:求最大公约数。
(define (gcd a b)(if (= b 0)a(gcd b (remainder a b))))
如何表达这个过程的时空呢?
空间——数据通路图
它需要保存两个值:a 和 b,我们用寄存器 a 和 b 来存储。
它需要以下操作:
检测寄存器 b 里的值是否等于 0
计算寄存器 a 除以 寄存器 b 里的值的余数
每次循环,替换寄存器 a 和 b 的值,替换寄存器 b 和余数——需要第三个寄存器 t。
如果我们把寄存器用矩形表示
每一次赋值给寄存器用箭头表示,
一个操作用梯形表示,比如 rem 表示求余数。
这个代码的空间操纵就可以表达成下图,我们称其为数据通路图。
请把 X 形想象成按钮,每次按下,就会运行值流向指定的寄存器。

寄存器值的来源可以是以下三种情况:
另一个寄存器(比如 a←b 赋值)
一次操作结果(比如 t←r 赋值)
或者是一个常数(图中用一个三角形表示)。
数据通路图显示了机器需要的寄存器和操作,以及它们是如何相连的。
如果把箭头想象成导线,X 按钮想象成开关,数据通路图就非常接近这个程序的硬件电路图了。一个芯片架构师都是从程序的数据通路图开始设计集成电路的。
比如,SICP第四章的解析器代码表达成的数据通路图最终翻译成的硬件集成电路就如下图所示,请注意这不是教学展示的模具,而是真实商用的CPU内核芯片。

时间——控制器图
虽然数据通路图表达了最大公约数程序的空间操纵,但是只有这个就够了吗?
虽然把箭头想象成导线,X 按钮想象成开关,就可以将数据通路图用逻辑电路来实现,但是,我们还需要表达这些按钮按下去的顺序——即,时间。
这就用控制器图来表示。
如果我们用矩形表示数据通路的按钮
箭头表示下一步的方向
菱形表示决策点
最大公约数的程序的时间就可以表达成下图所示,称之为控制器图。

如果将这个图想象成一个迷宫,每一个矩形是一个小房间,有一个小球从程序的起点开始运行,当运行到菱形决策点处有一个机关来判断它是否符合退出条件,那么这完全就是一个机器了。这就是为什么 MIT 的人工智能先贤们(SICP的作者)说:
“我的目的只是想向你证明,你眼中那些神秘莫测、晦涩繁杂的系统(芯片、编译系统、内核、人工智能…),
本质上只是一个钟表装置。”

表达时空的语言——解析器/编译器
空间和时间一起,就表达了完整的世界。
数据通路加上控制器一起,就表达了一个程序对应的硬件电路。
由于计算机中用寄存器来实现空间,用寄存器的操作指令序列来表达时间,因此计算机又称为寄存器机。

我们已经为求最大公约数的程序画出了数据通路图和控制器图。但是,当程序规模很大时,为它画出数据通路和控制器图显然不现实。
因此,我们需要创建一个语言,让机器来表达数据通路和控制器的所有信息。
从此,我们只需要使用这个语言,念动咒语,就可以命令精灵(机器)来替我们执行。
如何构建这个语言呢,请记住我们是创造者,是机器的甲方,因此我们的任务是表达我们的需求(这就是我命名的“甲方定律”,又称创造者定律,掌握了这个定律,我们就脱离了码农的身份,变成代码的创造者)。
要表达数据通路,必须表达寄存器和操作。
为了表达一个寄存器,必须表达其名字,以及它的赋值按钮。我们给这些按钮一个名字,并描述其来源。
为了表达一个操作,必须表达其名字,以及它的输入(来自寄存器或是常数)。
为了表达控制器,必须表达一个指令的序列,我们用标签(label)来标注序列中的接入点。
指令的种类有:
一个数据通路按钮的名字,按下即可为其赋值
一个 test 指令,执行某种测试
一个条件选择跳转 branch 指令,根据上一步测试的结果,指向一个标签。
一个无条件跳转 goto 指令,跳转到给定的标签。
因此,这个求最大公约数的程序,就可以用机器语言表达成如下所示:

这就是这个程序的机器代码了。
将人类读懂的高级抽象的语言翻译成机器读懂的寄存器操纵语言有两种方法:解析器和编译器。它们各有优势,现实商用计算机中往往采用混合的方案,各取所长。















































































