细说:程序运行的环境和运行过程,再看不懂请自行面壁

  编译器的任务,是要生成能够在计算机上运行的代码,但要生成代码,我们必须对程序的运行环境和运行机制有比较透彻的了解 。
你要知道,大型的、复杂一点儿的系统,比如像淘宝一样的电商系统、搜索引擎系统等等,都存在一些技术任务,是需要你深入了解底层机制才能解决的 。比如淘宝的基础技术团队就曾经贡献过,JAVA 虚拟机即时编译功能中的一个补丁 。
这反映出掌握底层技术能力的重要性,所以,如果你想进阶成为这个层次的工程师,不能只学学上层的语法,而是要把计算机语言从上层的语法到底层的运行机制都了解透彻 。
文本我会对计算机程序如何运行,做一个解密,话题分成两个部分:

  1. 了解程序运行的环境,包括 CPU、内存和操作系统,探知它们跟程序到底有什么关系 。
  2. 了解程序运行的过程 。比如,一个程序是怎么跑起来的,代码是怎样执行和跳转的,又是如何管理内存的 。
首先,我们先来了解一下程序运行的环境 。
程序运行的环境程序运行的过程中,主要是跟两个硬件(CPU 和内存)以及一个软件(操作系统)打交道 。
细说:程序运行的环境和运行过程,再看不懂请自行面壁

文章插图
 
本质上,我们的程序只关心 CPU 和内存这两个硬件 。你可能说:“不对啊,计算机还有其他硬件,比如显示器和硬盘啊 。”但对我们的程序来说,操作这些硬件,也只是执行某些特定的驱动代码,跟执行其他代码并没有什么差异 。
1. 关注CPU和内存CPU 的内部有很多组成部分,对于本文来说,我们重点关注的是寄存器以及高速缓存,它们跟程序的执行机制和优化密切相关 。
寄存器是 CPU 指令在进行计算的时候,临时数据存储的地方 。CPU 指令一般都会用到寄存器,比如,典型的一个加法计算(c=a+b)的过程是这样的:
指令 1(mov):从内取 a 的值放到寄存器中;指令 2(add):再把内存中 b 的值取出来与这个寄存器中的值相加,仍然保存在寄存器中;指令 3(mov):最后再把寄存器中的数据写回内存中 c 的地址 。
寄存器的速度也很快,所以能用寄存器就别用内存 。尽量充分利用寄存器,是编译器做优化的内容之一 。
而高速缓存可以弥补 CPU 的处理速度和内存访问速度之间的差距 。所以,我们的指令在内存读一个数据的时候,它不是老老实实地只读进当前指令所需要的数据,而是把跟这个数据相邻的一组数据都读进高速缓存了 。这就相当于外卖小哥送餐的时候,不会为每一单来回跑一趟,而是一次取一批,如果这一批外卖恰好都是同一个写字楼里的,那小哥的送餐效率就会很高 。
内存和高速缓存的速度差异差不多是两个数量级,也就是一百倍 。比如,高速缓存的读取时间可能是 0.5ns,而内存的访问时间可能是 50ns 。不同硬件的参数可能有差异,但总体来说是几十倍到上百倍的差异 。
你写程序时,尽量把某个操作所需的数据都放在内存中的连续区域中,不要零零散散地到处放,这样有利于充分利用高速缓存 。这种优化思路,叫做数据的局部性 。
这里提一句,在写系统级的程序时,你要对各种 IO 的时间有基本的概念,比如高速缓存、内存、磁盘、网络的 IO 大致都是什么数量级的 。因为这都影响到系统的整体性能,也影响到你如何做程序优化 。如果你需要对程序做更多的优化,还需要了解更多的 CPU 运行机制,包括流水线机制、并行机制等等,这里就不展开了 。
讲完 CPU 之后,还有内存这个硬件 。
程序在运行时,操作系统会给它分配一块虚拟的内存空间,让它在运行期可以使用 。我们目前使用的都是 64 位的机器,你可以用一个 64 位的长整型来表示内存地址,它能够表示的所有地址,我们叫做寻址空间 。
64 位机器的寻址空间就有 2 的 64 次方那么大,也就是有很多很多个 T(Terabyte),大到你的程序根本用不完 。不过,操作系统一般会给予一定的限制,不会给你这么大的寻址空间,比如给到 100 来个 G,这对一般的程序,也足够用了 。
在存在操作系统的情况下,程序逻辑上可使用的内存一般大于实际的物理内存 。程序在使用内存的时候,操作系统会把程序使用的逻辑地址映射到真实的物理内存地址 。有的物理内存区域会映射进多个进程的地址空间 。
细说:程序运行的环境和运行过程,再看不懂请自行面壁

文章插图


推荐阅读