查看原文
其他

漫谈计算机硬件体系对编程的影响和意义

阿驹 驹说码事 2024-03-30

今天由于时间不多,难有旁征博引的长文来跟大家解释清楚计算机、存储,及其对编程的影响。不过,有一点自己微小的见解与大家分享。虽然微小,但确实给自己减轻了很多在技术方面的理解成本和成长负担。个人觉得也应是有些分享价值的。

1. 计算机组成

现代电子计算机从二十世纪四十年代末发展至今,以EDVAC计算机为标志,到目前为止67年间计算机硬件体系的核心都未曾发生过变化,都是冯诺依曼体系的。注意这里说的是EDVAC而非第一台通用计算机ENIAC。EDVAC于1949年交付,1951年正式运行,1961年完成使命退出历史舞台。

1.1 题外话:编程语言与软件系统的发展和自举

本小节的目的是为了解决大家常有的疑惑:现代编程语言和软件都是逐步迭代的方式演进,那最早最早的编程语言和操作系统是怎么来的? 越是追溯事物的起源,越是能发现其本质。

先提及一下EDVAC服役的这几年(1949-1961)间出现过的,至今影响深远的编程语言。在这12年间,与硬件平台强相关的特定汇编于1949年最早出现,1951年各种汇编方言开始兴起。Fortran于1954年开始开发,1957年正式由IBM发布。约翰·麦卡锡于1958年基于IPL语言发明了Lisp函数式编程语言,主要用于人工智能。同年, ALGOL也诞生了。1959年COBOL也问世。Fortran、Lisp、ALGOL,就是后来几乎所有高级编程语言的鼻祖,它们至今自己也发挥着巨大的作用。

最早的都是用机器语言通过穿孔卡片的形式搞出来的,像Fortran早期都是穿孔卡片进行编程。后来的新语言和新系统,都是基于前辈们自举而来。有了编程语言之后,程序,包括操作系统程序是如何诞生出来的就可想而知。

编程语言和软件自举的过程。纯手工用穿孔纸的形式用机器语言编写了早期的具有特定功能的程序,比如存粹为了计算导弹等军事用途的程序。后来为了编程方便,各类汇编方言诞生,所以同样纯手工机器码编程写个汇编翻译器,能把汇编源码翻译成机器语言,故而就能用汇编语言来写各种各样的特定功能的程序了。因为汇编语言翻译之后得到的最终程序是机器可直接执行的机器码,所以用汇编1.0写个汇编2.0翻译器也没问题的,汇编2.0成熟后,就能完全抛弃汇编汇编1.0了。同样的,直接用机器码或者汇编语言,写个A语言的编译器也没问题,A语言编译器稳定之后,也可以用A语言来写A语言自己的编译器,用汇编写的A语言编译器就可以抛弃了。

如雷贯耳的C语言,就是按上述过程基于CPL和BCPL语言发展而来的,后来很多语言用C写第一版编译器也不足为奇了,比如Python官方实现就一直用的是C,而Pypy是用Python自举的。

1.2 冯诺依曼体系精要

首先要明确一点,冯诺依曼体系因其设计EDVAC时而系统提出来的,但是其中的核心思想和观念,绝不是冯诺依曼一人灵光乍现的个人杰作。有很多前辈们的努力,冯诺依曼是那个有机会总结并在大型项目中实践的幸运儿。

1.2.1 两个核心原则:

  • 计算机程序和待处理的数据无差别存储在存储器中
     这里隐含了两点,运行时存在哪里?掉电后存在哪里?也就是主存和外存,冯诺依曼的报告中是有专门提及主存和外存的。更进一步说,程序亦是数据的一种
  • 使用二进制,而非十进制
     ENIAC使用的就是十进制。

1.2.2 五个组成部分

  • 运算器、控制器
  • 存储器
  • 输入设备、输出设备

1.2.3 更简化地理解

在本人理解来看,计算机抛开外围设备后,最核心的东西就是运算器和存储器。 简言之,运算与存储。控制器是可以单独列出来的,但是由于控制器本质就是根据不同的判定条件选择不同的处理过程,和一般的运算没有根本上的区别,我把它粗略地归为运算类。这样归纳也是有理由的,因为在早期的机电计算机中,有运算器和存储器就完全可以工作了(后来有了晶体管的计算机称为电子计算机)。

这样的简化的作用是为了减轻对计算机体系的理解成本,如何个减轻法,请继续看后文。

1.3 计算机组成总结

关键字:程序亦数据、二进制、运算器、存储器。

其中进制不必多谈,以后会发文专门谈谈为何非要是二进制在这种体系结构下最恰当,可不仅仅是高电平、低电平那么表面的解释。程序亦是数据,这原本是指冯诺依曼体系中程序和数据无差别对待,一起存放在存储器里就行。但我们可以从这条延伸出很多编程上的思想,程序就是指令集,指令等同于数据对待时,问题就会简化很多,也会灵活很多。

函数式编程语言说函数是一等公民,而一个函数本就是一段程序,当有了“程序亦数据”的思想后,拿函数作为入参传来传去不是很自然地事吗?不知道当初Lisp的诞生,有没有受到这一点的启发,也许Lisp纯粹是按数学上对待函数的态度来的。不过应了我常说那句话:世界是圆的。

同理,面向对象编程的语言中,一个对象本就是一组数据和其操作的封装,把对象当做参数传来传去也在情理之中。命令式语言把命令当做数据操作,这本就是它们的工作方式,由计算机硬件组成结构而来的工作方式。

2. 存储

曾经在”细学Python”QQ群里问群友,大家觉得存储的本质是为了什么?回答五花八门,有的说是为了赚钱让公司不倒闭,那为什么、凭什么能赚钱了?有的说是为了再利用,再利用来干什么?有的说是为了备份。有的说是为了能给程序处理,各种各种。这些回答都是有道理的,但是从一种“事物原本”的角度来讲,不是很到位。

不论是外存、内存、数据库软件、浏览器端的Cookie、服务端的Session、磁盘上的文件等等,乃至是超越计算机体系之外,人脑中的记忆,各种各样,五花八门的存储形式,都有一个共同点:为了状态的延续。不论是从运行原理上、业务逻辑上、商业目的上等各种层次各种维度来观察,这个结论都能得到解释。

浏览器端的Cookie和服务端的Session不必多说,为了使用户的登录状态得以延续;程序运行时在内存中的数据存储是为了程序的运行时状态得以延续;电脑关机以后要将数据数据回写磁盘,是为了下次开机后能够延续上一次关机前的开机状态;数据库中存储的数据,是为了让业务系统的运行时状态、业务逻辑状态不断延续;人脑能够根据存储的记忆触景生情,是为了在若干时间后延续之前的那种情绪状态,比如运动员若干年后看着自己曾经拿到的金牌能够置身于当初得奖的喜悦感受中。还有很多例子拿来解释。

3. 硬件体系和存储的本对编程的意义

在上一篇《编程到底是个什么玩意》中已经说了一条很重要的原则:事物是分层次的,软件也是。层次思维明晰的话,就容易思考清楚很多软件中的架构和设计。

3.1 运算器与存储器对编程的意义

编程语言各种各样,之前我们已经谈过了程序的共同本质,那么硬件体系对编程有什么影响呢?首先是硬件的可能性直接决定了软件的可能性。其次,既然硬件体系可归纳为两大类:运算器与存储器。那么软件体系也逃不脱这两大类,做运算的与做存储的。

运算与存储,就好比是两个基本元素,它们在硬件体系中存在,也在软件体系中存在。它们还能够按层次组织,大层次的元素可以囊括小层次的元素。举个例子:MySQL数据库从宏观看是一个存储程序,而要让MySQL这个级别的程序正确工作,只有一个层次是不可能的,它必须包含运算类的模块(按上文所述,控制类的也属于此),也包含了存储类的模块,还有各种不同的层次逐级分下来,最终落实到MySQL的每一行源代码,哪函数是是它所在的那个级别的存储作用的,哪个函数是该级别的运算作用的;某个函数中,哪一行数和存取数据相关的,哪一行是与运算数据相关的……最终,指令而且还会落实到硬件上的运算器与存储器上。

由于存储到底还是存储的是数据,故而明白了存储的地位以后,就知道了计算机程序所操作的存储器与存储器中的数据的重要性。程序要操作的存储器是内存,而数据在编程语言中的表现形式就是该语言支持的各种数据结构,包括基本数据结构和扩展数据结构,基本数据结构是就是整数、浮点数等,扩展数据结构包括结构体、对象等等。故而学习一门编程新语言,你首要关注的是如何进行内存的分配与使用,以及数据结构的操作。

还有一个很明确的编程指导思路:从程序功能讲,你所要完成的项目应该归为运算类还是存储类?当要做模块拆分时,这两种分类又能指导你进行模块划分。为了更清晰,你可以按运算器、控制器、存储器三种来分。再往下可以指导你的类、函数的划分,再往下就能指导代码的编写,哪一行该操作哪个数据对象,它存在于哪里?要经过怎么样的运算,要到哪里去?……

当软件需要优化时,有两个大方向可以考虑,是运算需要优化,还是存储需要优化?假如当运算需要优化时,哪个级别负责运算的程序需要优化?要优化这个级别的运算,那其子级别中的运算和存储两大类程序又是哪类更应该优化?当程序功能不足需要添加功能时,要添加充当运算角色的功能呢?还是要添加充当控制角色的?抑或是要充当存储功能的?……

有的同学可能会想,像WEB界面的内容,应该归为哪类?宏观看,WEB界面只是服务端输出的结果,通过网络I/O这种方式暂时存放到了客户端,浏览器把这些数据处理渲染给了终端用户。网络I/O属于我理解的计算机外围设备,当然,非要划分个归类,那应当归为存储类。

总而言之,运算与存储这两个基本元素,构建起了冯诺依曼体系下计算机软硬件的高楼大厦。

3.2 状态延续性对编程的意义

无需多言,将会指导我们定义程序中的数据种类、数据结构,优化数据的的核心指南。你希望程序这一刻是个什么状态?经过运算后,下一刻又是一个什么状态?要不要延续?那些基本数据齐活了以后才能使这个状态得以延续?如果1个字节的数据就能让状态延续下去,用1KB数据来解决问题是不是有些浪费?

在有些程序里,磁盘IO速度不足,满足不了CPU的处理速度,那怎么办?那是CPU里的计算状态延续不下去了,出现等待磁盘IO的断档,很明显的优化思路就是增加一种程序来解决这个事情。增加运算类的程序能解决这个问题吗?看样子不太行,应为CPU作为运算器的存在它已经有强大优势了。那就加存储类程序呗,用内存来做存储嘛,好像问题可以得解了。

如果随着程序发展,又满足不了了怎么办?换性能更好的存储器,一种存储器解决不了的问题就两种存储器解决,两种存储器解决不了的就三种来解决…… 君不见,这就是冯诺依曼体系下的硬件发展与软件优化趋势,硬件中的多级缓存,从CPU到内存到磁盘,软件中的多级缓存从应用程序到数据库程序到操作系统程序,无处不在……

以后再发文说说多级缓存的事。

另外,存储是为了状态延续这条指导规则,还可以发挥更多的作用,比如在数据灾难恢复、程序不间断服务而切换存储介质或者切换数据库上。大家可以发挥一下,不再多写。

4. 总结

本文也许有些同学看起来就是“假大空”,原因之一是确实篇幅有限,不能再多啰嗦;其二是偏理论经验性的阐述,而缺少实例;其三是这部分同学可能编程经验还不足。

如果本文引起了你的共鸣,那就不枉我写这一场了。记得在前一篇文章中说过的一些学习思路,看月亮,别看手指,就算是看到了月亮,也要看到一类月亮,而非一个月亮。不知这个梗的同学可以翻看本号上一篇文章。

如果觉得本文像压缩饼干,请辅以开水泡了吃;若觉像蓬松面包,在挤压也总是有点货的。

继续滑动看下一个
向上滑动看下一个

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存