《代码的未来》读书笔记——多核时代的编程

代码的未来什么也不用考虑,随着时间的推移CPU自然会变得越来越快,这样的趋势也快要接近极限了。长期以来,软件开发者一直受到硬件进步的恩惠,即便不进行任何优化,随着计算机的更新换代,同样的价格所能够买到的性能也越来越高。不过,现在即便换了新的计算机,有时也并不能带来直接的性能提升。要想提升性能,则必须要积极运用多核以及CPU的新特性。

最近,GPGPU(General Purpose GPU,即将GPU用于图形处理之外的通用编程)受到了越来越多的关注,由于GPU与传统CPU的计算模型有着本质的区别,因此需要采用专门的编程技术。

即便什么都不做,CPU也会变得越来越快的时代结束了,今后为了活用新的硬件,软件开发者必须要付出更多的努力——这样的情况,我将其称为“免费午餐的终结”。

在未来的软件开发中,如果不能了解CPU的新趋势,就无法提高性能。新的计算设备必然需要新的计算模型,而这样的时代已经到来。

P304

多核时代的管道

在UNIX诞生的20世纪60年代末,多核CPU还不存在,因此管道原本的设计也并非以运用多核为前提。然而,不知是偶然还是必然,管道对于多核的运用却是非常有效的。

首先,我们来思考一下非常原始的单任务操作系统,例如MS-DOS。在MS-DOS中,同时只能有一个进程在工作,因此管道是通过临时文件来实现的。

接下来我们来思考单核环境下的多任务操作系统。在这样的环境下,管道的命令是并行执行的。但由于只有一个核心,因此无法做到完全同时进行。进程会在不断相互切换中各自执行,数据输出以接力棒的形式进行运作,多个进程交替工作,就是单核多任务环境中的执行方式。

和单任务相比,多任务环境下的又是在于没有了无谓的文件输入输出操作,从而削减了相应的开销。
而且,由于多个进程是依次执行的,先得出的结果会立即通过管道传递,因此获取结果也会比较快一些。

不过,在多任务环境下,进程的切换也需要一定的开销,从总体来看,执行时间也未必会缩短。

接下来终于要讲到多核环境下的管道了。简单起见,在这里我们假设将两个任务分别分配给两个不同的核心,在这样的情况下,同时执行的部分增多了,理想状态下应该比单核缩短一半,但这样的理想状态是很难实现的。

假设操作系统足够聪明的前提下,只要增加管道的级数,是能够重叠的部分也相应增加,即便不特意去管多个核心的配置,只要自然编写程序形成管道,操作系统就会自动利用多个核心来提高处理能力。之所以说串流管道是非常适合多核的一种编程模型,原因也正是在于此。

P308

阿姆达尔定律

阿姆达尔定律是一个估算通过多核并行能够获得多少性能提升的经验法则,是由吉恩·阿姆达尔(Gene Amdahl,1922~ )提出的,它的内容是:

(通过并行计算所获得的)系统性能提升效果,会随着无法并行的部分而产生饱和。

即便是多核计算机,一般也只有一个输入输出控制器,而这个部分无法获得并行计算所带来的效果,很容易成为瓶颈。

而且,当数据之间存在相互依赖关系时,在所依赖的数据准备好之前,即便有空闲的核心也无法开始工作,这也会成为瓶颈。

综上所述,大多数的处理都不具备“只要增加核心就能够提高速度”这一良好的性质,这一点与在CPU内部实现流水线的艰辛似乎存在一定的相似性。

P311

非阻塞I/O

在需要处理大量连接的服务器上,如果使用线程的话,内存负荷和线程切换的开销都会变得非常巨大。因此,监听“有输入进来”等事件并进行应对处理,采用单线程来实现会更加高效。像这样通过“事件及应对处理”的方式来工作的软件架构,被称为事件驱动模型(event driven model)。

像这样处理发生停滞的情况被称为阻塞。阻塞多半会在等待输入输出的时候发生。对于事件驱动型程序来说,阻塞是应当极力避免的。

由于大部分输入输出操作都免不了会遇到阻塞,因此在输入输出时需要尤其注意。输入输出操作并不快,因此需要进行缓冲。当数据到达缓冲区时,读取操作只需要从缓冲区中将数据复制出来就可以了。

在缓冲机制中,有两种情况会产生等待。一种是当缓冲区为空时,需要等待数据到达缓冲区(读取时);另一种是在缓冲区已满时,需要等待缓冲区腾出空间(写入时)。这两种“等待”就相当于程序停止工作的“阻塞”状态。

尤其是在输入(读取)时,如果在数据到达前试图执行读取操作,就会一直等待数据的到达,这样肯定会发生阻塞。

相比之下,输出时由于磁盘写入、网络传输等因素,也有可能会发生阻塞,但发生的概率并不高。而且即便发生了阻塞,等待时间也相对较短,因此不必过于在意。

在事件的监视中,对时间的检测方法有两种,即边沿触发(edge trigger)和电平触发(level trigger)。这两个词原本是用在机械控制领域中的,边沿触发是指只在状态变化的瞬间发出通知,而电平触发是指在状态发生变化的整个过程中都持续发出通知。

要让程序在不阻塞的状态下工作,事件监视就必须采用电平触发的方式。也就是说,在调用回调函数执行输入操作之后,如果读取缓冲区中还有残留的数据,在电平触发的方式下,就会再次调用回调函数来进行读取操作。

那么,采用电平触发就足够了吗?边沿触发的存在还有什么意义呢?由于边沿触发只在数据到达的瞬间产生事件,因此总体来看事件发生的次数会比较少,这也就意味着回调函数的启动次数会比较少,可以提高效率。

P316

事件驱动编程

在传统的过程型编程中,各项操作时按照预先设定好的顺序来执行的。这与人类完成工作的一般方式相同,因此很容易理解。

相对地,在事件驱动框架所提供的事件驱动编程中,不存在事先设定好的工作顺序,而是对来自外部的“事件”做出响应,并调用与该事件相对应的“回调函数”。这里所说的事件,包括“来自外部的输入”、“到达事先设定的时间”、“发生异常情况”等情况。在事件循环框架中,主循环程序一般是用一个循环来等待事件的发生,当检测到事件发生时,找到并启动与该事件相对应的处理程序(回调函数)。当回调函数运行完毕后,在此返回到循环中,等待下一个事件。

P334

发表评论

您的电子邮箱地址不会被公开。