多核应用编程实战[试读]
第一章:硬件、进程和线程
要编写串行或并行应用程序,并非一定要了解硬件的工作原理,编写代码时完全可以将计算机内部结构视作黑盒子。然而,对处理器内部结构作一个基本了解会使稍后的主题更直观易懂。串行(或单线程)应用程序和并行(或多线程)应用程序之间的关键区别在于,由于多线程的存在,更多系统属性变得对应用程序重要起来。例如,单线程应用程序不会出现多个线程争用相同资源的情况,但对多线程应用程序来说这种情况经常发生。资源可能是缓存空间、内存带宽,或者仅仅是物理内存空间,在这些情况下,硬件特性会反映在应用程序行为的变化上。若对硬件的工作方式有所了解,读者会更容易理解、诊断和纠正应用程序的任何异常行为。... 查看全部[ 第一章:硬件、进程和线程 ]
1.1 计算机的内部结构
从根本上讲,一台计算机包括一个或多个处理器以及一些内存,它们通过一些芯片和电线连接起来。此外,还有磁盘驱动器或网卡等辅助设备。 图1-1显示了一台个人计算机的内部结构。计算机由数个元件组成,处理器和内存插在称为主板的电路板上,从主板上伸出的电线与磁盘驱动器、DVD光驱等辅助设备相连。视频、网络支持等功能则集成到主板上或通过插卡提供。 如以图表示信息,可能更容易让人了解系统各元件的关联,参见图1-2。在此示意图中,我们将系统的计算部分与辅助设备分开表示。 系统的计算性能主要由处理器和内存的性能决定,而计算性能又将决定计算机执行指令的速度快慢。 辅助设备的性能不是我们所关注的,因为辅助设备... 查看全部[ 1.1 计算机的内部结构 ]
1.2 多核处理器的缘起
微处理器已存在相当长时间了。x86架构可追溯至1978年发布的8086,而SPARC架构则出现得稍晚,第一个SPARC处理器于1987年面世。在那段时间,性能提升多源自加快处理器时钟速度(最初的8086处理器时钟频率约为5 MHz,而最新处理器的时钟频率约是其600倍,超过3 GHz)和架构方面的改进(同时发出多个指令等)。然而,最近的处理器却增加了CPU芯片上的核心数量,而非强调单个线程在处理器上运行时的性能提升。处理器核心是执行应用程序指令的部分,因此有了多个核心,单个处理器就能同时执行多个应用程序。 变更为多核处理器的原因很容易理解:提高串行性能日益困难。为使处理器更快地执行指令,要使... 查看全部[ 1.2 多核处理器的缘起 ]
1.2.1 在单芯片上支持多线程
处理器的核心是芯片上负责执行指令的部分。核心有许多组成部分,我们将在本章稍后详细讨论其中某些部分。处理器的简化原理图见图1-3。 缓存是芯片上保存最近使用数据和指令的部分。查看一下处理器内的硅片(如图1-7所示),可以看到核心和缓存是肉眼可见的两个元件。关于缓存,请参见1.2.3节。 让芯片运行多线程最简单的方式就是多次复制同一核心,如图1-4所示。最早能支持多线程的处理器就得益于这种方法。这也是多核处理器的基本思想。这是一种简单的方法,因为此法只需采用现有处理器的设计并加以复制,两个核心之间以及内核和系统之间的通信涉及的情况较为复杂,但核心(这是处理器最复杂的部分)的变化最小。两个核... 查看全部[ 1.2.1 在单芯片上支持多线程 ]
1.2.2 通过处理器核心流水线作业提高指令发出率
正如我们前面所说的,处理器的核心是处理器中负责执行指令的部分。早期的处理器每个时钟周期执行单条指令,因此运行频率为4 MHz的处理器每秒钟可执行400万条指令。执行单条指令的逻辑可能相当复杂,所以执行最长指令所用的时间决定了一个时钟周期的长度,并因此确定了处理器的最高时钟速度。 为了改进这种情况,出现了“流水线”处理器设计。完成单条指令所需的操作被划分为多个较小的步骤。下面是最简单的流水线。 提取 从内存中提取下一条指令。 解码 确定该指令的类型。 执行 完成相应工作。 完成 使指令状态的变化对系统其余部分可见。 假设完成指令所需的总时间保持不变,则4个步骤中任何一个步骤都占用原时... 查看全部[ 1.2.2 通过处理器核心流水线作业提高指令发出率 ]
1.2.3 使用缓存保存最近使用的数据
处理器请求从内存获取一组字节时,最终获得的不仅仅是这些字节。从内存中提取数据时,数据连同其相邻字节作为一条缓存线一起提取,如图1-12所示。根据系统中处理器的不同,缓存线可小至16字节,也可大至128字节或更多。缓存线的典型大小为64字节。缓存线总是对齐的,所以64字节缓存线的起始地址是64的倍数。因为这种设计决策优化了系统,使系统总是传递64字节大小、对齐的数据,所以简化了系统;另一种设计决策则采用远为复杂的内存接口,不得不处理不同尺寸的内存块和未对齐的起始地址。 从内存中提取的数据行存储于缓存中。缓存能提高性能,因为处理器很可能重复使用该数据或访问存储于同一缓存线中的数据。通常有指令... 查看全部[ 1.2.3 使用缓存保存最近使用的数据 ]
1.2.4 用虚拟内存存储数据
运行中的应用程序使用所谓的虚拟内存地址来保存数据。数据仍然保存在内存中,但应用程序使用的不是数据保存在内存芯片中的确切地址,而是虚拟地址,该虚拟地址随后被转换为物理内存的实际地址。图1-16是从虚拟内存转换为物理内存的过程示意图。 这种使用内存的方式看似复杂得毫无必要,但确有不少相当重要的优点。 虚拟内存最初的目的是使处理器能在比系统上的物理内存更大的内存范围进行寻址,推出这个技术的时候,物理内存价格极为昂贵。虚拟内存的工作原理是以页面为单位分配内存,每个页面可能保存在物理内存中或存储在磁盘上。当要访问的地址不在物理内存中时,如某个内存页面包含一段时间内未被使用的数据,机器就将该页面写... 查看全部[ 1.2.4 用虚拟内存存储数据 ]
1.2.5 从虚拟地址转换到物理地址
使用虚拟内存的关键步骤是把应用程序使用的虚拟地址转换为处理器使用的物理地址,以便从内存中提取数据。这一步通过处理器上称为TLB(Translation Look-aside Buffer,转换旁视缓冲器)的部分实现。通常,一个TLB用于转换指令地址(指令TLB,或称ITLB),另一个TLB则用于转换数据地址(数据TLB,或称DTLB)。 每个TLB都是一个列表,包含每个内存页面的虚拟地址范围以及对应的物理地址范围。因此,当需要将虚拟地址转换为物理地址时,处理器首先将虚拟地址划分为虚拟页面地址(高位)和相对于页面起始地址的页内偏移量(低位),然后在TLB保存的转换列表中查找此虚拟页面的地址,获... 查看全部[ 1.2.5 从虚拟地址转换到物理地址 ]
1.3 多处理器系统的特征
虽然现在多核处理器已经极为普遍,但带有多处理器的系统也同样日益普遍。只要一个系统有多个处理器,内存访问就会变得更加复杂。数据不仅可以保存在内存中,而且也可以保存在其他处理器的缓存中。为使代码能正确执行,缓存中应该只有每个数据项的最新版本,这一功能称为缓存一致性。 提供缓存一致性的通常做法是窥探。每个处理器广播它要读取或写入的地址,其他处理器监听这些广播,一旦看到数据地址为自己所持有,可以采取以下两个动作:如果广播处理器要读取数据且监听处理器有最新的副本,则监听处理器可以返回数据;如果广播处理器要存储数据的最新值,则监听处理器使其副本失效。 然而,这并非处理多处理器时面临的唯一问题。其他问题... 查看全部[ 1.3 多处理器系统的特征 ]
1.4 源代码到汇编语言的转换
处理器执行指令,指令是所有计算的基本构建块,执行如加法、从内存提取数据或将数据存储回内存等任务。指令在寄存器上进行操作,寄存器保存变量的当前值和其他机器状态。考虑代码清单1-1所示的代码段,其功能为递增指针ptr指向的整型变量。 代码清单1-1 将某个地址的变量+1的代码 void func( int * ptr ) { ( *ptr )++; } 代码清单1-2显示了此代码段编译为SPARC汇编代码的结果。 代码清单1-2 将某个地址的变量+1的SPARC汇编代码 ld [%o0], %o5 //将地址%o0的值加载到寄存器%o5 add ... 查看全部[ 1.4 源代码到汇编语言的转换 ]
1.4.1 32位与64位代码的性能
从理论上说,一个64位处理器可以对多达16 EB(艾字节)的物理内存(4 GB的平方)寻址。相比之下,32位处理器只能对最多4 GB内存寻址。对于有些应用程序来说,只能对4 GB内存寻址是一种限制,例如轻易就会超过4 GB的数据库。因此,升级至64位寻址就能实现对更大数据集的操作。 X86处理器的64位指令集扩展有AMD64、EMT64,x86-64(简称x64),这些指令集扩展不仅增加了处理器能寻址的内存,而且还通过消除或减轻两大问题提升了性能。 解决的第一个问题是基于栈的调用约定。此约定导致代码不得不用大量的存储和加载指令将参数传入函数。在32位代码中,当函数被调用时,其所有参数都需要... 查看全部[ 1.4.1 32位与64位代码的性能 ]
1.4.2 确保内存操作的正确顺序
当系统包含多个处理器或多个核心时,还有一个需要讨论的问题:内存排序。内存排序是内存操作对于系统中其他处理器可见的顺序。大部分时间,处理器无需程序员的任何干预即能正确处理事务。 然而,有些情况下程序员确实需要介入。这些情况可能与具体的架构(SPARC处理器和x86处理器有不同的要求)或具体的实施(不同类型的SPARC处理器可能有不同的需求)有关。好消息是由于系统库实施合理的机制,使用系统库的多线程应用程序不会遇到此类问题。 另一方面,调用系统库有一些开销,因此很可能出于性能方面的动机而编写自定义的同步代码。这种情况将在第8章中阐述。 内存排序指令在SPARC上称为内存屏障(membar),... 查看全部[ 1.4.2 确保内存操作的正确顺序 ]
1.4.3 进程和线程的差异
对进程和线程如何构成软件及如何映射到内存进行探究颇有益处。本节将介绍一些在未来几章常会提到的概念。应用程序包括指令和数据,在其开始运行之前,这些只是一些分布在磁盘上的指令和数据,如图1-21所示。 正在执行的应用程序称为进程,进程不仅仅是指令和数据,它还有状态。状态是保存在处理器寄存器中的一组值,如当前执行指令的地址、保存在内存中的值,以及唯一定义进程在任一时刻任务的所有其他值。进程与应用程序的一个重要的区别在于,进程运行时,进程的状态会发生变化。图1-22显示了在内存上运行的应用程序的布局。 进程是应用程序的基本构建块。同时运行的多个应用程序实际上就是多个进程。要支持多个用户,通... 查看全部[ 1.4.3 进程和线程的差异 ]
1.5 小结
本章介绍了一些处理器架构的术语。要关注的重点在于如何将缓存用于提升应用程序的性能和怎样利用TLB才能实现对虚拟内存的使用。本章还介绍了在单个处理器上支持多个线程的多种方式。尽管这种资源共享的实施细节在高层次上的抽象并不重要,我们仍将在稍后讨论这些细节如何对性能产生明显影响。最后,本章描述了进程和软件线程是如何映射到内存的,以及多线程应用程序和多进程应用程序之间的重要差异。... 查看全部[ 1.5 小结 ]
书名: 多核应用编程实战
作者: [美] Darryl Gove
出版社: 人民邮电出版社
原作名: Multicore application programming:for windows,linux,and Oracle Solaris
副标题: 多核应用编程实战
译者: 郭晴霞
出版年: 2013-6
页数: 345
定价: 79.00元
装帧: 平装
ISBN: 9787115317506