程序执行中的多个流

发布时间 2024-01-05 11:02:05作者: deeplearnMs

任何运行于现代操作系统中的程序都会与同时运行的其他程序、检查磁盘或者新的 Java
Flash 版本的定期维护进程以及控制网络接口、磁盘、声音设备、加速器、温度计和其他
外设的操作系统的各个部分共享计算机。每个程序都会与其他程序竞争计算机资源。

程序不会过多在意这些事情。它只是会运行得稍微慢一点而已。不过有一个例外,那就是
当许多程序一齐开始运行,互相竞争内存和磁盘时。为了性能调优,如果一个程序必须在
启动时执行或是在负载高峰期时执行,那么在测量性能时也必须带上负载。
2016 年早期,台式计算机有多达 16 个处理器核心。手机和平板电脑中的微处理器也有
多达 8 个核心。但是,快速地浏览下 Windows 的任务管理器、Linux 的进程状态输出结果
Android 的任务列表就可以发现,微处理器所执行的软件进程远比这个数量大,而且绝
大多数进程都有多个线程在执行。操作系统会执行一个线程一段很短的时间,然后将上下
文切换至其他线程或进程。对程序而言,就仿佛执行一条语句花费了一纳秒,但执行下一
条语句花费了 60 毫秒

 

切换上下文究竟是什么意思呢?如果操作系统正在将一个线程切换至同一个程序的另外一
个线程,这表示要为即将暂停的线程保存处理器中的寄存器,然后为即将被继续执行的线
程加载之前保存过的寄存器。现代处理器中的寄存器包含数百字节的数据。当新线程继续
执行时,它的数据可能并不在高速缓存中,所以当加载新的上下文到高速缓存中时,会有
一个缓慢的初始化阶段。因此,切换线程上下文的成本很高。
当操作系统从一个程序切换至另外一个程序时,这个过程的开销会更加昂贵。所有脏的高
速缓存页面(页面被入了数据,但还没有反映到主内存中)都必须被刷新至物理内存中。
所有的处理器寄存器都需要被保存。然后,内存管理器中的“物理地址到虚拟地址”的内
存页寄存器也需要被保存。接着,新线程的“物理地址到虚拟地址”的内存页寄存器和处
理器寄存器被载入。最后就可以继续执行了。但是这时高速缓存是空的,因此在高速缓存
被填充满之前,还有一段缓慢且需要激烈地竞争内存的初始化阶段。
当一个程序必须等某个事件发生时,它甚至可能会在这个事件发生后继续等待,直至操作
系统让处理器为继续执行程序做好准备。这会导致当程序运行于其他程序的上下文中,竞
争计算机资源时,程序的运行时间变得更长和更加难以确定。
为了能够达到更好的性能,一个多核处理器的执行单元及相关的高速缓存,与其他的执行
单元及相关的高速缓存都是或多或少互相独立的。不过,所有的执行单元都共享同样的主
内存。执行单元必须竞争使用那些将可以它们链接至主内存的硬件,使得在拥有多个执行
单元的计算机中,冯 诺依曼瓶颈的限制变得更加明显。
当执行单元写值时,这个值会首先进入高速缓存内存。不过最终,这个值将被写入至主内
存中,这样其他所有的执行单元就都可以看见这个值了。但是,这些执行单元在访问主内
存时存在着竞争,所以可能在执行单元改变了一个值,然后又执行几百个指令后,主内存
中的值才会被更新。
因此,如果一台计算机有多个执行单元,那么一个执行单元可能需要在很长一段时间后才
能看见另一个执行单元所写的数据被反映至主内存中,而且主内存发生改变的顺序可能与
指令的执行顺序不一样。受到不可预测的时间因素的干扰,执行单元看到的共享内存字中
的值可能是旧的,也可能是被更新后的值。这时,必须使用特殊的同步指令来确保运行于
不同执行单元间的线程看到的内存中的值是一致的。对优化而言,这意味着访问线程间的
共享数据比访问非共享数据要慢得多。