潘爱民:计算机程序的演进——我的程序人生三十年

作者:亿网科技  来源:亿网科技  发布时间:2022-04-08

我在 1985 年第一次接触到计算机程序,尽管那只是娃娃机上的一些作业型的 BASIC 程序,但我依然感受到了编程的乐趣,并且乐此不疲地编写各种花样程序。三十多年过去,计算机程序于我而言已经成为一种思考方式,无论是应用层的功能,还是系统层的能力,或者是背后的数据处理逻辑,都转化成了机器的指令。

我有幸完整地经历了从 PC 时代,到互联网的兴起与发展,再到移动互联网,又到万物互联时代和产业数字化的发展历程。这三十多年,既是软件技术和产业的发展期,也是我个人的职业生涯和程序人生。本文介绍我认知中的计算机程序的演进,正好是程序人生三十年的一篇小结。

潘爱民(摄于 1999 年北大工作期间)

软件栈 —— 从源代码到机器指令

通常而言,计算机程序是指软件中的代码部分。软件涵盖的内容要多得多,比如还包括数据、文档,甚至有硬件(比如加密狗),还可能有相应的服务等。计算机程序则是指一组指示计算机或其他具有信息处理能力的装置执行各种动作的指令。计算机程序既可以是机器指令的形式(由二进制数 0 和 1 构成的序列,人难以解读),也可以是人编写的原始代码的形式(通常由程序员来解读和维护)。

过去三十多年软件技术的发展,可以从程序编写方式和运行方式来看待其中的变化。典型的有以下一些方式:

1. 代码直译执行

早期的程序编写方式是,程序员按照机器执行指令的思路来控制一台机器。最典型的是用 C 语言来编写程序,几乎每一行代码都可以对应到一个指令序列,甚至可以在 C 语言源代码中直接嵌入汇编指令(机器指令的字符描述方式)。

2. 代码解释执行

原始的代码被解释成一种中间抽象语言描述,再进一步转换成机器语言被执行。以 Java 语言的哲学思想“一次编写,到处运行(Write once, run anywhere.)”为基础,用 Java 语言编写的程序天然具有跨平台特性。程序员面对的是一个抽象的计算环境,所编写的 Java 代码是如何被执行的,这中间有一个间接层。

3. 虚拟机和容器化

程序员编写的代码最终由机器上的 CPU 来执行,随着计算机硬件能力越来越强,一台机器可以被虚拟成多台计算机。源代码被编译或者解释出来的机器代码,又进一步被映射成一个指令序列。云计算的发展和普及让这种程序运行方式变成了主流。容器是虚拟机的一种轻量形式,其思想本质上是一致的。

4. 前后端分离

数据显示用 Web 技术来完成,后端处理用合适的编程语言来完成,两者之间通过符合 Web 标准的 API 进行通信。将可视化和用户交互部分单独划出来,而不是跟业务逻辑耦合在一起,这是前后端分离的核心思想。

以上四种方式,前三种侧重于计算机程序在同一台机器上被引入了一个或者多个中间层,导致源代码被多次解释或映射才到达物理 CPU;第四种方式则是从横向跨机器的角度将可视化和用户交互部分与业务逻辑(特别是数据处理)解耦。这些变化与软件产业的发展息息相关。下面几点值得一提。

从源代码到对应的机器指令,路径越来越不清晰。在物理机器环境下,源代码与 CPU 之间隔了一层或多层;在云计算环境下,程序员甚至完全不知道物理上代码是如何被执行的。这种变化趋势导致了程序调试和性能优化变得更加复杂和困难。

当程序运行时,业务功能的代码路径变长,甚至分布到不同的计算环境中。一个业务逻辑被触发的地点,与它被处理的地点,之间可能跨了执行环境,甚至跨了网络。这对于程序员的复合能力要求更高,单一技术栈的程序员将面临很大的挑战。

这些编程方式是融合的,有各自的适应场景。随着互联网络的不断发展,并深入到各个产业中,混合编程方式已经成为软件行业的主流。对于软件开发人员,我们需要在深度和广度上并进:所谓深度,是指对源代码往下一层递进执行的理解;所谓广度,是指对执行链从一个执行环境到另一个执行环境迁移过程的理解。

技术的进步是产业发展的内在动力,云计算得益于硬件的极大进步以及虚拟化技术的成熟。反过来,产业的发展又带动了软件从业人员的大规模扩大。中国的程序员有数百万,甚至按有的统计途径达到了上千万。这些编程方式的发展与软件产业的效率有极大的关系。新的软件技术进步,有可能成倍地提高产业效率,进而又可能导致大量的从业人员需要进行技术变迁。

我始终认为,程序员写代码是一种创造活动,这是程序员职业的神圣之处。理想的情况是,程序员所写的代码都是创造性的,并且是高价值的,是机器人和人工智能所无法企及的。否则的话,这些工作迟早会被技术的革新取代。如果程序员编写的代码只是一些定式化的重复,或者可以用相对简单的形式化来描述,那么按如今的技术演进,这些代码可以不用人来编写了。这是目前很多低代码或零代码开发平台正在努力的目标,也导致了这样一个趋势:写工具是非常有价值的,而不依赖于工具来写大量逻辑的工作是价值有限的。

研究生期间在宿舍写代码

在我职业生涯的前半段,我一直信奉“用二进制的方式来理解程序或系统”,为每一个功能或任务都揣摩底层的指令序列。随着程序或系统的复杂性增加、云计算及前后端分离模式的普及,我们越来越无法做到精细化地用二进制方式来理解它们了。在这种情况下,对软件架构的把握变得越来越重要,机缘巧合之下我实现了从系统程序员到软件架构师的升级。

网络 —— 无处不在的连接

网络的发展改变了我们的生活,这是过去将近三十年人类社会最大的变化之一。网络也同样改变了计算机程序的运行方式,甚至改变了编程的思想。我们首先看一下网络本身的演变:

初期的网络是在机房环境或者办公环境中使用的,网络的物理形态非常直观,因为大多数情况下每台机器都拖着一根线。常用的网络功能都通过专门的应用程序来完成,比如电子邮件程序、浏览器、文件传输工具等。网络功能的程序编写属于高级编程技术。

网络开始普及,家庭和很多公共场所都有了网络,连接的方式可以是一根线,也可以是 Wi-Fi 无线。在这样的条件下,越来越多的程序加上了网络的能力,网络编程逐渐普及,但仍然属于高级编程技术。不过,通过很多中间件,编程门槛已经降低。

移动数据网络的普及。网络的进步是全面的,包括硬件基础设施和软件栈。移动数据网络相对不稳定,这对于软件编程是一个挑战,其复杂性来自于两个方面:对网络异常的处理,以及网络连接涉及到两方协同。然而,得益于移动操作系统原生提供的网络基础功能,网络编程的门槛被大大降低。

其次,网络的思想与操作系统密不可分,两者的演进更是紧密关联。网络的核心哲学思想是协议分层,每一层的功能实现只依赖于下一层提供的语义,同时也为上一层提供标准或约定的语义。操作系统也有类似的分层结构,层次越往上,离硬件越远;越往上,编程的门槛越低。网络的软件栈大部分位于操作系统中,相对应地,因网络带来的复杂性大部分由操作系统消化掉了,因此应用程序的编写并不显著地变得更加困难。譬如,移动数据网络带来的复杂性,绝大部分由移动操作系统处理了,它对上面提供的应用开发框架中并没有暴露出移动数据网络环境的复杂性。

然而,网络本身对于应用程序的编写还是有极大影响的,从软件设计到代码编写都有深远的影响,以下是一些显著的变化点。

网络编程最基础的模式是异步编程,以及对异常的处理和恢复。TCP 和 UDP 不仅是两个传输协议,更是两种编程思想。它们指导我们如何设计和编写服务器程序和客户程序。

对于在网络环境下运行的程序,我们编写的可能只是半个程序。另外半个程序可能在完全不熟识的人手里进行开发,可能在地球的另一侧,甚至不在地球上。我们需要遵守网络双方的约定、遵守对方的规范,或者要有足够的灵活性来应对可能的意外。

网络功能容易遭受性能和体验的问题。网络往往是一个共享资源,所以它的不稳定通常是可以预料的,编写网络功能的时候若处理得好,就有可能化解掉不稳定的因素;若处理不恰当,就会造成性能极差,乃至体验极差,甚至进程死锁或崩溃等。从这个角度而言,网络编程总是有优化改进的空间。

网络的不稳定带来了不确定性,导致网络程序的诊断变得困难。一方面,程序的网络环境可能会有抖动,导致网络行为可能无法重现,从而增加了诊断难度;另一方面,网络程序可能运行在物理上不可达的环境(比如云主机)中,这要求程序员对网络环境的理解要更加全面,否则难以从现象或错误代码来分析问题的根源。

计算机网络经历了大量的技术择优和淘汰,今天我们享受到的稳定网络和良好的网络应用程序是历史沉淀的结果。硬件上,我们的网络越来越稳定,无线网络的基站(或访问点)之间可以做到无缝切换;软件上,操作系统解决了大量的网络复杂性问题,留给应用程序的是相对容易实现的处理逻辑,比如方便处理的 HTTP 协议、无状态的远程请求、自动的离线缓存等等。在移动数据网络的早期,很多应用程序一遇到网络不稳定,就出现白屏、不响应,为了改善用户体验,应用开发人员需要编写大量的代码。随着移动数据网络的普及和稳定,以及移动操作系统的成熟,这一类应用代码已经大大减少了。

随着移动互联网的发展,我们又迈进了万物互联时代,产业数字化如火如荼地进行。网络上连接的已经不再限于计算机或者个人设备,越来越多各种各样的设备都连接到互联网上。网络技术和操作系统都面临一次升级,从概念到功能外延都在发生变化。我有幸在从业这么多年以后又赶上一次技术浪潮。在这一轮技术大升级中,面向设备连接的操作系统应运而生,因而我在 2018 年创立了指令集公司,专门从事物联网操作系统的研发和商业化。

人工智能 —— 从模拟智能到超越人类智能

人工智能的发展代表了人类使用计算的一种追求。计算是一种能力,可以做很多事情,包括科学计算和事务型的任务等;其中人工智能的任务是指,让机器通过计算,可以像人类一样拥有智能。自从计算机诞生以来,人工智能的发展经历了起起落落,但过去三十年间,人工智能学科总体上一直是在向前发展的,下面是人工智能领域的一些典型事件:

深蓝计算机(IBM 制造),1997 年,深蓝击败人类象棋冠军卡斯帕罗夫。

仿生机器人“大狗”(波士顿动力学工程公司研制),2005 年,可以四条腿行走。

阿尔法围棋(AlphaGo,Google 研发),2016 年,击败人类围棋冠军李世石。

人脸识别技术应用于移动 App,例如,2015 年马云在 CeBIT(德国)展会上演示了刷脸支付。

AlphaFold/AlphaFold2(Google 研发),2020/2021 年,基本上攻克了困扰人类科学家已经很久的预测蛋白质折叠结构的问题。

此外,最近 10 年来,大多数汽车制造企业(无论是传统车企,还是新势力造车企业)以及一些互联网科技公司都在研究自动驾驶汽车,并且陆续有一些自动驾驶汽车上市。从以上这些事件我们可以看出,人工智能应用有很多种探索路径:

模拟智能

将人类的思考过程,利用计算能力进行模拟。比较典型的是象棋和围棋这一类规则化的智力活动,人类的思考过程可以恰当地提炼出来。因此,只要有足够的存储和算力支撑,以及人类的经验模型,就有机会做得比人类还好。

利用算法实现智能任务

在许多应用场景中,可利用人工智能算法(主要是深度学习算法)来完成一些明确定义的任务,比如人脸识别、车牌识别、语音识别等等。这一类人工智能应用需要具备两个条件:足够多的样本和足够强的算力。在过去十年中,移动互联网的蓬勃发展使得很多业务场景汇聚了足够多的样本数据,再结合云计算的发展,因而这一类人工智能应用发展迅速。

综合替代人类,达到人类智能

比较典型的是自动驾驶汽车,以及各种具有复杂决策能力的机器人。自动驾驶汽车可以将人从驾驶任务中解放出来,机器人可以代替人类进入到复杂场景中执行任务。这一类人工智能应用需要综合各种软硬件技术,近几年在产业界是一个科创热点。

超越人类智能

探索未知领域,造福人类。比较典型的是在一些科研领域,结合了人工智能的技术以后获得了革命性的突破,例如上文提到的 AlphaFold2 使蛋白质折叠结构预测问题得到了突破,达到了原本人类通过实验无法做到的结果。

人工智能的核心三要素是数据、算力和算法。算力是计算的物理基础,数据是计算的原料,算法是计算的逻辑,其最终形式即软件代码。人工智能的发展催生了大量的数据工程师和算法工程师岗位。数据工程师负责采集数据,对它们进行各种处理,归集起来以供算法使用;算法工程师负责实现各种算法,或者调用一些通用的算法来完成特定的任务。经过多年的发展,目前有很多算法库已经沉淀下来,有不少以开源的方式供业界使用,例如 TensorFlow、PyTorch、Ray 和 SparkML 等。

算法的编程尤其要关注性能,以确保算法的性能足够优,这不是一项简单的任务,它需要扎实的底层系统知识,甚至要理解硬件架构。一方面,数据的传递和分布对于一个大计算量的算法是非常重要的;另一方面,在众多计算节点中,要避免出现单点性能瓶颈。有很多的领域专家在使用人工智能算法时,并不洞悉底层计算平台的配置要求,或者未能正确地使用计算库,从而造成资源浪费或者计算时间过长,这在实践中较为常见。

我有机会在之江实验室建设一个大计算装置,称为智能计算数字反应堆,其旨在搭建一个大计算平台。该数字反应堆可以聚合多种异构算力资源,并通过一些计算框架或者算法库,为各种应用(包括科学发现、数字经济、工业仿真等,称为应用反应堆)提供统一的计算平台。可以想象,一旦有了这样的计算设施,将人工智能算法与各个领域模型结合起来,在局部领域超越人类智能将会成为一种发展模式。

可视化和用户交互 —— 从 GUI 到数字孪生

在应用软件开发工程中,可视化和用户交互部分往往会占据相当大的比例。写这部分程序逻辑?