Python并行编程手册(txt+pdf+epub+mobi电子书下载)


发布时间:2020-08-03 15:12:59

点击下载

作者:张龙,宋秉金(译)

出版社:电子工业出版社

格式: AZW3, DOCX, EPUB, MOBI, PDF, TXT

Python并行编程手册

Python并行编程手册试读:

前言

计算机科学的研究不仅应该涵盖计算处理所基于的原则,还应该反映出这些领域当前的知识状态。时至今日,技术的发展要求来自计算机科学所有分支领域的专家通晓软件与硬件,它们之间的交互是理解计算处理基本原理的关键所在。

出于这个原因,本书特别关注硬件架构与软件之间的关系。

之前,程序员可以借助于硬件设计、编译器与芯片让其软件程序在不做任何修改的情况下,速度变得更快或效率更高。

这个时代已然结束。现在,如果希望程序运行速度能变得更快,那么它必须要成为一个并行程序。

虽然很多研究人员的目标是程序员不需要了解运行其程序的硬件所具有的并行性,不过要想实现这个梦想还需要很多年的时间。今天,大多数程序员还是需要透彻理解硬件与软件之间的联系,这样程序才能在现代计算机架构上高效运行。

为了介绍并行编程的概念,我们使用了Python编程语言。Python很有趣,且易于使用,其流行度在近几年稳步上升。Python是由Guido van Rossum在10多年前开发出来的,Python语法的简洁性与易于使用的特性很大程度上来源于ABC,这是一门于20世纪80年代开发出来的教学语言。

除了这个具体的上下文外,Python还被用于解决实际的问题,它从一些典型的编程语言中借鉴了很多特性,比如说C++、Java与Scheme等。这是其最卓越的特性之一,使得它广受专业软件开发者、科学研究产业以及计算机科学教育者的青睐。之所以有这么多人喜欢Python,一个原因是它在实践与概念之间提供了最佳的平衡。Python是一门解释型语言,因此无须陷入编译与链接的泥潭中就可以立刻动手做事情。Python还提供了广泛的软件库,可用在从Web开发、图形学到并行计算的各种任务中。这种侧重于实践的特性非常吸引广大读者,可以让大家运行本书所介绍的重要项目。

本书提供了大量的示例,这些示例的灵感来源于很多场景,可以用它们解决实际问题。本书介绍了并行架构的软件设计原则,并确保程序清晰可读,避免使用一些复杂技术,都是一些简单且直接的示例。每个主题都以一个完整、可运行的Python程序的一个片段来阐述,后面跟着的是程序的输出结果。

书中各章节采用模块化的方式组织,可以从最简单的讨论跳到高级的主题,这么做也适合于那些只想学习一些特定主题的读者。

我希望本书这样的结构与内容安排能够帮助读者更好地理解和掌握并行编程技术。

本书主要内容

第1章概览了并行编程架构与编程模型。本章介绍了Python编程语言、语言的特性、其易于使用和学习的特点、可扩展性以及丰富的软件库与应用。此外,本章还介绍了如何在应用以及并行计算中用好Python这个工具。

第2章介绍了如何通过Python线程模块来实现线程并行。通过完整的编程示例,读者将学习到如何同步和操纵线程来实现多线程应用。

第3章介绍了基于进程的用于并行化程序的方式。本章通过完整的示例展示了如何使用Python多线程模块。此外,本章还介绍了如何通过进程来实现通信,借助Python的mpi4py模块使用消息传递的并行编程范式。

第4章介绍了并发编程的异步模式。在某些方面,它要比基于线程的方式简单一些,因为它提供了单指令流,任务会明确放弃控制而非武断地挂起。本章介绍了如何通过Python asyncio模块将每个任务组织为一系列更小的步骤,并在异步模式下执行。

第5章介绍了分布式计算。它指的是从逻辑上(甚至可能是地理位置上分布的)聚合几个计算单元,并以一种透明且一致的方式协同运行单个计算任务的过程。本章将会介绍Python所提供的,使用面向对象、Celery、SCOOP、远程过程调用的架构实现解决方案,比如,Pyro4与RPyC。本章也介绍了其他不同的方式,如PyCSP、Disco,后者是Python版本的MapReduce算法。

第6章介绍了现代图形处理单元(GPU),它使数值计算的性能有了突破性提升,代价则是编程复杂度的增加。实际上,GPU编程模型要求程序员手工管理CPU与GPU之间的数据传输。本章将会通过程序示例与用例介绍如何使用GPU卡所提供的计算能力,其中使用了强大的Python模块:PyCUDA、NumbaPro与PyOpenlCL。

阅读前的准备

本书所有示例都已经在Windows 7的32位机器上进行过测试。此外,使用Linux环境将会对学习大有裨益。

运行示例所需的Python版本是:

■ Python 3.3(前5章)

■ Python 2.7(只有第6章需要)

需要使用到如下模块(都是可以自由下载的):

■ mpich-3.1.4

■ pip 6.1.1

■ mpi4py1.3.1

■ asyncio 3.4.3

■ Celery 3.1.18

■ Numpy 1.9.2

■ Flower 0.8.32(可选)

■ SCOOP 0.7.2

■ Pyro 4.4.36

■ PyCSP 0.9.0

■ DISCO 0.5.2

■ RPyC 3.3.0

■ PyCUDA 2015.1.2

■ CUDA Toolkit 4.2.9(最低为此版本)

■ NVIDIA GPU SDK 4.2.9(最低为此版本)

■ NVIDIA GPU驱动

■ Microsoft Visual Studio 2008 C++Express Edition(最低为此版本)

■ Anaconda Python Distribution

■ NumbaPro编译器

■ PyOpenCL 2015.1

■ Win32 OpenCL Driver 15.1(最低为此版本)

本书面向的读者

本书是写给那些想要使用并行编程技术来编写强大且高效代码的开发者的。阅读完本书后,读者将掌握并行计算的基础知识与高级特性。Python编程语言易于学习,即便不是专家也能够轻松理解本书所介绍的主题。

本书结构

本书包含如下组成部分。

准备工作

这部分介绍了攻略的主题,描述了如何为该攻略搭建好软件或是进行一些设置。

具体操作

这部分介绍了实现攻略所需要的步骤。

实例精解

这部分通常会对“具体操作”部分的内容进行解释和说明。

知识扩展

这部分包含了关于攻略的一些额外信息,目的在于使读者更加深入地理解攻略的内容。

参考

这部分包含一些关于攻略的参考信息。

约定

在本书中,你会看到各种样式的文本,用于区分不同类型的信息。下面对这些文本样式及其含义进行一些说明。

文本中的代码、数据库表名、目录名、文件名、文件扩展、路径名、虚拟的URL、用户输入以及Twitter handle会这样表示“要执行第一个示例,我们需要用到程序helloPython-WithThreads.py.”。

代码块样式如下所示:

希望读者注意到代码块中的某一部分时,相关行或是代码会加粗处理:

任何命令行输入或是输出的样式如下所示:

读者服务

轻松注册成为博文视点社区用户(www.broadview.com.cn),扫码直达本书页面。

■ 下载资源:本书如提供示例代码及资源文件,均可在下载资源处下载。

■ 提交勘误:你对书中内容的修改意见可在提交勘误处提交,若被采纳,将获赠博文视点社区积分(在购买电子书时,积分可用来抵扣相应金额)。

■ 交流互动:在页面下方读者评论处留下你的疑问或观点,与我们和其他读者一同学习交流。

页面入口:http://www.broadview.com.cn/337531 并行计算与Python起步

本章主要内容:

▼ 何为并行计算

▼ 并行计算内存架构

▼ 内存组织

▼ 并行编程模型

▼ 如何设计并行程序

▼ 如何评估并行程序的性能

▼ Python简介

▼ 并行世界中的Python

▼ 进程与线程介绍

▼ 开始使用进程与Python

▼ 开始使用线程与Python介绍

本章将会概述并行编程架构与编程模型。这些概念对于初次接触并行编程技术、经验不太丰富的程序员来说非常有价值。对于有经验的程序员来说,本章内容可以作为一个基本的参考。本章还会介绍并行系统的双重特性。第一个特性基于系统架构,第二个特性基于并行编程范式。对于程序员来说,并行编程总是充满挑战的。这种基于编程的方式还会在本章后面介绍并行程序的设计过程时进一步阐述。本章最后将会对Python编程语言进行简短的介绍。这门语言的诸多特性(比如易于使用和学习,可扩展性以及丰富的软件库与应用)使得它对于任何应用来说都是一个颇有价值的工具,当然,对于并行计算亦如此。本章的最后一部分将会介绍线程与进程的概念,以及它们在Python语言中的使用。解决大问题的一种典型方式是将其分解为一系列小问题以及独立的部分,这样就可以同时解决它们了。并行程序就是针对使用这种方式的程序而设计的,也就是说,对于一个一般性任务来说,使用多个处理器同时工作。每个处理器都处理属于它自己的那部分问题(独立的部分)。此外,处理器之间的数据信息交换可以发生在计算过程中。时至今日,很多软件应用都需要更多的计算能力。达成该目标的一种方式就是提高处理器的时钟频率或是增加每个芯片上的处理器核心数。提高时钟频率会增加散热,从而降低每瓦特的性能;此外,这还需要特殊的冷却设备。增加核心数只不过是一个看起来可行的方案,因为能量功耗与消耗还远远没有达到极限,在性能上并没有那么明显的提升。

为了解决这个问题,计算机硬件厂商决定采用多核架构,即单个芯片上包含两个或多个处理器(核心)。另一方面,GPU制造商还引入了基于多计算核心的硬件架构。实际上,现如今的计算机几乎都是采用多个以及异构的计算单元,每个单元都由不同数量的核心构成,比如,最常见的多核架构。

因此,我们非常有必要采用并行计算的编程范式、技术以及工具使用可用的计算资源来。并行计算内存架构

根据可同时处理的指令数量与数据量的不同,计算机系统可以划分为如下4类:

▼ 单指令,单数据(SISD)

▼ 单指令,多数据(SIMD)

▼ 多指令,单数据(MISD)

▼ 多指令,多数据(MIMD)

这种分类方式叫作费林分类(Flynn’s taxonomy),如下图所示。

SISD

SISD计算系统是一个单处理器机器,它所执行的每个指令都只会操作单个数据流。在SISD中,机器指令是顺序处理的。

在一个时钟周期中,CPU会执行如下操作。

▼ 获取:CPU从内存区域获取数据与指令,该内存区域叫作寄存器。

▼ 解码:CPU对指令进行解码。

▼ 执行:指令在数据上得到执行。操作结果会被存储到另一个寄存器中。

当执行阶段完成后,CPU会对自身进行设置,从而开始另一个CPU周期,如下图所示。SISD架构模式

在这类计算机中执行的算法是顺序的(又叫作串行),因为它们并不包含任何并行。只拥有单个CPU的硬件系统就是SISD计算机。

构成这种架构(冯·诺伊曼架构)的主要元素有如下几项。

▼ 中央存储单元:用于存储指令与程序数据。

▼ CPU:用于从存储单元中获取指令与(或)数据,它会对指令进行解码并顺序地执行它们。

▼ I/O系统:指的是程序的输入与输出数据。

传统的单处理器计算机被归类为SISD系统。下图特别展示了在获取、解码与执行阶段中分别用到了CPU的哪些区域。在获取-解码-执行阶段所用到的CPU组件

MISD

该模型中的n个处理器,每一个都有自己的控制单元,它们共享同一个存储单元,如下图所示。在每个时钟周期内,从内存接收到的数据会被所有处理器同时处理,每个处理器都会按照从其控制单元中所接收到的指令顺序进行处理。在这种情况下,通过对相同的数据执行几个操作实现了并行(指令级并行)。这种架构所能有效解决的问题是相当特殊的,比如说与数据加密相关的问题等;出于这个原因,计算机MISD并未在商业上流行起来。MISD计算机更多的是用在智力训练而非实际使用。MISD架构模式

SIMD

SIMD计算机包含n个相同的处理器,每个处理器都有自己的本地内存,可以用于存储数据。所有处理器都处于单个指令流的控制之下;此外,还有n个数据流,分别针对每个处理器。在每个步骤中,处理器都会同时工作并执行相同的指令,不过是在不同的数据元素上执行。这是一种数据级的并行。SIMD架构要比MISD架构更加通用。大量应用所涉及的诸多问题都可以通过SIMD计算机中的并行算法来解决。另一个有趣的特性是这些计算机所用算法的设计、分析与实现相对来说都比较容易。局限在于只有那些可以被分解为一系列子问题的问题(这些子问题要完全一样,每个子问题后面会通过相同的指令集同时解决)才能通过SIMD计算机来解决。对于根据该范式所开发出的超级计算机来说,我们必须要提到的就是Connection Machine (1985 Thinking Machine)与MPP(NASAü 1983)。第6章将会提到,拥有大量SIMD嵌入式单元的现代图形处理器单元(GPU)的出现使得这种计算范式得到了更为广泛的使用。

MIMD

根据费林的分类,这种并行计算机是最为通用,也是更为强大的一种,如下图所示。它有n个处理器、n个指令流以及n个数据流。每个处理器都有自己的控制单元与本地内存,从计算角度来说,这使得MIMD架构要比SIMD更加强大。每个处理器都是在它自己的控制单元所发出的指令流的控制下来进行操作的;因此,处理器可以对不同的数据执行不同的程序,可以将一个大问题分解为多个不同的子问题,然后加以解决。MIMD架构是通过线程与(或)进程级别的并行的帮助来实现的。这还意味着,处理器通常会异步执行。这类计算机用于解决那些拥有不规则结构的问题,而SIMD则要求问题的结构要规则才行。时至今日,这种架构已经应用到了很多PC、超级计算机以及计算机网络中。不过,有一点需要注意,异步算法的设计、分析与实现不是那么容易的事情。MIMD架构模式内存组织

在评估并行架构时需要考虑的另一个方面就是内存组织了,更准确地说是数据的访问方式。无论处理单元速度多么快,如果内存无法以足够的速度来维护并提供指令和数据,那么在性能上也不会有什么改进。要想让内存的响应时间跟得上处理器的速度,我们需要解决的主要问题就是存储周期时间,它指的是连续两个操作之间所经过的时间。处理器的周期时间通常要比内存的周期时间短。当处理器开始传输数据时(向内存传输或是从内存获取),内存将会在整个存储周期内被占用:在这期间,其他设备(I/O控制器、处理器,甚至是发出该请求的处理器自身)都无法使用内存,因为它要对请求做出响应。下图所示为MIMD架构中的内存组织。MIMD架构中的内存组织

对内存访问问题的解决方案导致了MIMD架构的分歧。在第一类系统中(即共享内存系统)有一个高层的虚拟内存,所有处理器都可以访问该内存中的数据与指令。另一类系统则使用了分布式内存模型,其中每个处理器都有自己的本地内存,其他处理器是无法访问的。共享内存与分布式内存之间的差别在于虚拟内存或是从处理器的视角所看到的内存结构。从物理上来说,几乎每个系统内存都会被划分为不同的组件,它们之间的访问是独立的。共享内存与分布式内存的区别则在于处理器单元的内存访问管理方式。如果处理器要执行指令load R0,i,这表示将内存位置i的内容加载到R0寄存器中,问题在于会发生什么呢?在共享内存系统中,i索引是一个全局地址,内存位置i对于每个处理器来说都是一样的。如果两个处理器同时执行该指令,那么它们就会在寄存器R0中加载相同的信息。在分布式内存系统中,i是一个本地地址。如果两个处理器同时加载语句R0,那么在各自的寄存器R0中就会有不同的值,因为在这种情况下,每个本地地址会被分配不同的内存单元。共享内存与分布式内存之间的差别对于开发者来说至关重要,因为它决定了并行程序的不同部分之间的通信方式。在一个系统中,共享内存足以在内存中构建数据结构,然后进入并行子程序,它们是该数据结构的引用变量。此外,分布式内存机器必须要在各自的本地内存中复制共享数据。这些副本是通过从一个处理器向另一个处理器发送包含数据的消息来创建的。这种内存组织的一个缺点在于,有时这些消息会非常大,需要花费相对较长的时间来传递。

共享内存

下图展示了共享内存多处理器系统的模式。这里的物理连接是相当简单的。总线结构可以支持共享相同通道的任意数量的设备。总线协议最初被设计为支持单个处理器,一个或多个磁盘以及磁带处理器可以通过这里的共享内存进行通信。注意,每个处理器都会有一个与之关联的高速缓存,因为很多时候处理器都需要本地内存中的数据或是指令。当一个处理器修改了同时被其他处理器所用的内存系统中的数据时就会产生问题。新值需要从处理器缓存中传递过来,而处理器缓存中的值已经被修改到了共享内存中;不过,接下来它还需要传递给所有其他的处理器,这样老的值就无法正常使用了。这个问题叫作缓存一致性问题,这是内存一致性的一个特例,需要硬件实现来处理并发问题与同步,类似于线程编程一样。共享内存架构模式

共享内存系统的主要特点如下所示。

▼ 内存对于所有处理器来说都是一样的,比如,与相同数据结构所关联的所有处理器都会使用同样的逻辑内存地址,这样就会访问到相同的内存位置。

▼ 同步是通过控制处理器对共享内存的访问来实现的。实际上,在某一时刻只有一个处理器能够访问到内存资源。

▼ 当一个任务在访问共享内存时,另一个任务是不可以修改共享内存的位置的。

▼ 数据共享是非常快的;两个任务间通信所需的时间等于读取单个内存位置的时间(取决于内存访问的速度)。

共享内存系统中的内存访问如下所示。

▼ 统一内存访问(UMA):该系统的基本特点是,对于每个处理器以及内存的任何区域来说,对内存的访问时间都是恒定的。出于这个原因,这些系统又叫作对称多处理器(SMP)。这些系统相对来说比较容易实现,不过可伸缩性并不好;程序员需要负责同步的管理,这是通过在管理资源的程序中插入恰当的控制、信号量以及锁来实现的。

▼ 非统一内存访问(NUMA):该架构将内存区域划分为高速访问区域(分配给每个处理器,是数据交换的公共区域)以及低速访问区域两种。这些系统又叫作分布式共享内存系统(DSM)。其可伸缩性非常好,不过开发起来难度颇大。

▼ 无远程内存访问(NORMA):内存在物理上被分配给各个处理器(本地内存)。所有的本地内存都是私有的,只能为本地处理器所访问。处理器之间的通信是通过用于消息交换的通信协议来实现的,叫作消息传递协议。

▼ 仅缓存访问(COMA):这些系统只有缓存。在分析NUMA架构时,我们知道,其架构是将数据的副本保存到缓存中,并且这些数据在主内存中还会有一个副本存在。该架构去除了主内存中的副本,只保留缓存,内存在物理上被分配给了各个处理器(即本地内存)。所有的本地内存都是私有的,只能为本地处理器所访问。处理器之间的通信是通过用于消息交换的通信协议来实现的,叫作消息传递协议。

分布式内存

在分布式内存系统中,内存与每个处理器关联到了一起,一个处理器只能访问到它自己的内存。一些作者将这类系统叫作“多计算机系统”,这反映了这样一个事实,即系统元素本身是小型且完备的处理器与内存系统,如下图所示。分布式内存架构模式

这种组织方式有几个优点。首先,在通信总线或是开关层面上不会再出现冲突。每个处理器都可以使用其自己本地内存的全部带宽而不会妨碍其他处理器。其次,无公共总线意味着对于处理器的数量不会再有固有的限制,系统的大小只受限于连接处理器的网络。第三,不存在缓存一致性的问题了。每个处理器负责管理自己的数据,不必再考虑副本的更新问题。主要的缺点则是处理器之间的通信更加难以实现。如果一个处理器需要另一个处理器内存中的数据,那么这两个处理器就需要通过消息传递协议来交换消息。这会引入两个速度降低之源;一个处理器构建消息并向另一个处理器发送消息需要时间;另外,处理器还得停下来管理从其他处理器所接收到的消息。针对分布式内存机器所设计的程序需要组织为一组独立的任务,这些任务间通过消息进行通信,如下图所示。基本的消息传递

分布式内存系统的主要特性如下所示。

▼ 物理上,内存在处理器之间是分布式的;每个本地内存都只会被其处理器所直接访问。

▼ 同步是通过在处理器间(通信)移动数据(即便只是消息本身亦如此)来实现的。

▼ 本地内存中数据的分割会影响机器的性能ü 划分的精确性非常重要,因为这样会将CPU之间的通信降到最低。除此之外,用于协调这些分解与组合操作的处理器必须能与对数据结构的每一部分进行操作的处理器高效通信。

▼ 使用消息传递协议,这样CPU就可以通过数据包的交换来彼此通信。消息是信息的离散单元;它们拥有定义明确的身份,这样就可以对其进行区分了。

大规模并行处理

MPP机器由成百上千个处理器组成(在一些机器上的规模可以达到成千上万),这些处理器之间通过通信网络进行连接。世界上最快的计算机就是基于这样的架构的,比如,Earth Simulator、Blue Gene、ASCI White、ASCI Red、ASCI Purple及Red Storm等。

工作站集群

这些处理系统基于传统计算机,它们之间通过通信网络进行连接。计算集群就属于这类,如下图所示。工作站集群架构示例

在集群架构中,我们将节点定义为集群中的单个计算单元。对于用户来说,集群是完全透明的ü 所有的硬件与软件的复杂性都被隐藏起来,我们在访问数据与应用时就好像它们都来自于单个节点一样。

下面介绍三类集群。

▼ 容错集群:在该类集群中,节点的活动会被持续监控。当一个节点停止工作时,另一台机器会接管它的活动。其目标旨在通过架构的冗余来确保持续的服务。

▼ 负载均衡集群:在该系统中,工作请求会被发送给活动较少的节点。这确保了完成整个过程所需的时间会更少一些。

▼ 高性能计算集群:在该类集群中,每个节点都会被配置以提供非常高的性能。整个过程依然会被划分为在多个节点上执行的多个任务。任务是并行化的,并且分布在不同的机器上。

异构架构

超级计算机同构世界中引入的GPU加速器改变了之前超级计算机使用与编程方式的本质。尽管GPU提供了很高的性能,不过它们无法作为一种自治的处理单元,因为它们总是要与CPU协同使用才行。因此,编程范式非常简单;CPU以串行方式进行控制与计算,将计算代价高昂并且需要很高并行度的任务分配给图形加速器。CPU与GPU之间的通信不仅可以通过高速总线来实现,还可以通过针对物理或是虚拟的单个内存区域的共享来实现。实际上,如果两台设备都没有自己的内存区域,那么可以通过各种编程模型所提供的软件库来访问共享内存区域,如CUDA和OpenCL。这种架构叫作异构架构,如下图所示,其中应用可以在单个地址空间中创建数据结构,并将任务发送给适合于其解析的设备硬件。多个处理任务可以在相同区域中安全地操作以避免数据一致性问题,这要归功于原子操作。因此,虽然CPU与GPU之间的协同效率不高,但借助这种新型架构,我们可以优化它们之间的交互以及并行应用的性能。异构架构模式并行编程模型

并行编程模型是硬件与内存架构的一种抽象。实际上,这些模型并非特定的,也没有指代特定类型的机器或是内存架构。它们可在任何类型的机器上实现(至少理论上如此)。相比于之前的划分来说,这些编程模型位于更高的层级上,表示软件必须被实现为执行一种并行计算的方式。每种模型都有与其他处理器共享信息的方式,从而访问内存并划分任务。

并不存在最好的编程模型,只有最适合程序员所要解决的问题的模型。最广泛使用的并行编程模型有:

▼ 共享内存模型

▼ 多线程模型

▼ 分布式内存/消息传递模型

▼ 数据并行模型

接下来介绍这些模型。后面的章节将会给出更为精确的说明,那时我们将会介绍用于实现它们的适合的Python模块。

共享内存模型

在该模型中,任务共享单个共享的内存区域,对共享资源的访问(读写数据)是异步的。有机制可以让程序员控制对共享内存的访问,比如锁或信号量。该模型的优势在于程序员不必清楚任务间的通信。从性能角度来说,该模型的一个严重缺点是使理解与管理数据的局部性变得更为困难;让数据成为使用它的处理器的局部数据可以减少内存访问、缓存刷新,以及多个处理器使用相同数据时所产生的总线流量。

多线程模型

在该模型中,一个进程可以拥有多个执行流,比如,先创建一个顺序部分,随后创建一系列任务并行执行。通常,这种模型会用在共享内存架构中。因此,对于我们来说,管理线程间的同步就是非常重要的事情了,因为这些线程会操作共享内存,程序员必须防止多个线程同时更新相同的位置。现代的CPU在软件与硬件上都是多线程的。Posix线程就是软件多线程实现的经典例子。英特尔的超线程技术实现了硬件的多线程,如果一个线程停下来或是等待I/O操作,那么它会切换至另外一个线程。即便数据对齐是非线性的,我们也可以通过该模型实现并行。

消息传递模型

消息传递模型通常用在每个处理器都有自己的内存(分布式内存系统)的场景下。更多的任务可以驻留在同一台物理机或是任意数量的机器上。程序员负责决定并行以及通过消息所进行的数据交换。该并行编程模型的实现需要在代码中用到特殊的软件库。目前已经有了大量的消息传递模型的实现:一些在20世纪80年代就出现了,不过直到20世纪90年代中期才形成标准化模型,成为名为MPI(消息传递接口)的事实上的标准。MPI模型的设计使用了分布式内存,但却成为并行编程的模型,多个平台也可以使用共享内存机器。消息传递范式模型如下图所示。消息传递范式模型

数据并行模型

在该模型中,多个任务可以操作相同的数据结构,不过每个任务都只会操作数据的不同部分。在共享内存架构中,所有的任务都可以通过共享内存与分布式内存架构来访问数据,其中的数据结构会被分割并驻留在每个任务的本地内存中。为了实现该模型,程序员需要开发一个程序来指定数据的分布与对齐方式。现代GPU在数据对齐的情况下处理速度非常快。数据并行范式模型如下图所示。数据并行范式模型如何设计并行程序

针对并行的算法设计基于一系列操作,程序要通过该算法正确地执行任务而不会生成部分或是错误的结果。对于一个算法来说,正确的并行化需要执行的宏观操作有:

▼ 任务分解

▼ 任务分配

▼ 聚集

▼ 映射

任务分解

这是第一个阶段,在该阶段,软件程序会被分解为任务或是一组指令,接下来在不同的处理器上执行以实现并行化。为了完成这个分解,可以使用以下两种方法。

▼ 领域分解:将问题数据进行分解;应用对于使用不同部分数据的所有处理器来说是公共的。当待处理的数据量很大时通常会使用该方法。

▼ 功能性分解:在这种情况下,问题会被分解为任务,每个任务都会在所有可用数据上执行特定的操作。

任务分配

在这个步骤中,指定好将任务分发到各个处理器上的机制。这个阶段非常重要,因为它会在各个处理器上建立工作负载的分配机制。在这里,负载均衡尤为重要;实际上,所有处理器必须要不间断地工作,从而避免较长时间的闲置状态。为了做到这一点,程序员需要考虑到系统之间可能存在的异构性,从而在性能更好的处理器上分配更多的任务。最后,要想获得更高的并行效率,要尽可能地限制处理器之间的通信,因为这常常是速度变慢以及资源消耗之源。

聚集

聚集指的是将小任务与大任务合并到一起从而改进性能的过程。如果设计过程的前两个阶段将问题划分为一系列任务,但任务数量远远超过可用的处理器数量,同时计算机并未针对处理大量的小任务而进行特别的设计(有些架构如GPU则可以很好地解决这个问题,实际上还会从运行数以百万甚至是数以亿计的任务中获益),那么设计就是极其低效的。一般来说,这是因为任务需要与处理器或是线程进行通信,这样它们才能计算任务。大多数通信的代价不仅与所传输的数据量有关,每次通信操作也都有固定的代价(比如建立TCP连接时固有的延迟)。如果任务过小,那么这种固定的代价就很容易使设计变得毫无效率可言。

映射

在并行算法设计过程的映射阶段,我们指定哪个任务将要执行。其目标是将总执行时间降到最低。这里必须要做出权衡,因为两个主要的策略之间经常会彼此冲突:

▼ 通信频繁的任务应该被放到同一个处理器中来增加局部性。

▼ 可以并发执行的任务应该被放到不同的处理器中以增强并发性。

这就是映射问题,即NP完备。这样,一般来说,并不存在针对问题的多项式时间解决方案。对于相同大小的任务以及很容易确定通信模式的任务来说,映射是很直接的(还可以执行聚集以将映射到相同处理器的任务合并起来)。不过,如果任务的通信模式很难预测或是每个任务的工作量大小千差万别,那么就很难设计出一种高效的映射与聚集模式了。对于这类问题,我们可以通过负载均衡算法在运行期确定聚集与映射策略。最难的是那种在程序执行期间通信量或是任务量发生变化的情况。对于这类问题,可以使用动态的负载均衡算法,它会在执行期间周期性地运行。

动态映射

不同的问题存在着多种负载均衡算法,有全局的,也有局部的。全局算法需要对待执行的计算有全局的掌控,这通常会增加大量的成本。局部算法只依赖于特定于任务本身的信息,相比于全局算法来说,这降低了成本,不过在寻找最优的聚集与映射时情况会变得更糟。不过,虽然映射本身效果更差,但降低的成本却会减少执行时间。如果除了执行开始或是结束外任务之间很少通信,那么我们就可使用任务调度算法来简化处理器空闲时将任务映射到其上的操作。在任务调度算法中会维护一个任务池,任务会被放到这个池中,并由执行者取出。

该模型中存在3种常见的方式,下面将会介绍。

管理者/执行者

这是一种基本的动态映射模式,所有的使用者都会连接到中心化的管理者。管理者会不断向使用者发送任务并收集结果。这种策略对于数量较少的处理器来说可能是最适合的。可以通过提前获取任务使得通信与计算之间彼此重叠来改进该策略。

层次化的管理者/执行者

这是管理者/执行者的一个变种,它有一个半分布式的层次;执行者被划分到组里,每个都与自己的管理者相关联。这些组管理者会与中央管理者进行通信(组管理者之间也可以通信),同时执行者会从组管理者那里请求任务。这会将负载分配到几个管理者上,这样如果所有执行者都从同一个管理者请求任务,那么它就可以处理更多数量的处理器了。

去中心化

在这种模式中,一切都是去中心化的。每个处理器会维护自己的任务池,并与其他处理器通信来请求任务。一个处理器选择其他处理器来请求任务的方式是不同的,这是根据问题来决定的。如何评估并行程序的性能

并行编程的发展使得性能度量与评估并行算法性能的软件成为刚需,这样才可以确定其使用起来方便与否。实际上,并行计算的关注点在于在相对较短的时间内解决大规模问题。影响该目标的因素有所用的硬件类型、问题的并行度以及所用的并行编程模型等。为了实现这一点,我们引入了基本的概念分析,它会比较从原始序列所获得的并行算法。其性能是通过分析与量化所用的线程数与进程数来达成的。

为了分析这一点,我们引入了一些性能指标:加速、效率与可伸缩性。

阿姆达尔定律提出了并行计算的限制,即由一个串行算法的并行度所决定的,此外,我们还有古斯塔夫定律。

加速

加速是一种度量方式,用于展示以并行方式解决问题所带来的好处。它的定义是,在单个处理元素上解决一个问题所花费的时间TS除以在p个相同的处理元素上解决同样问题所花费的时间T。p

我们用来表示加速。如果S=p,那么加速就是线性的,这表示如果处理器数量增加,那么执行速度也会增加。当然,这是理想情况。当T是最佳的串行算法执行时间时,加速是绝对的;当T是单SS个处理器上并行算法的执行时间时,加速是相对的。

下面再来看看这些条件:

▼ S=p是线性的或理想状况下的加速。

▼ S<p是真实的加速。

▼ S>p是超线性加速。

效率

在理想世界中,拥有p个处理元素的并行系统的加速等于p。不过,这是非常难以实现的。通常情况下,一些时间会浪费在空闲或是通信上。效率是一个性能度量,它会估算相比于在通信与同步上所浪费的时间来说,处理器在解决一个任务时的利用率。

我们将其表示为E,定义为。线性加速算法的E值为1;在其他情况下,E值要小于1。如下列出了E值的3种情况:

▼ 当E=1时,它是线性的。

▼ 当E<1时,它是真实情况的。

▼ 当E<<1时,这是一个并行效率很低的问题。

可伸缩性

可伸缩性指的是并行机器的效能。它是计算能力(执行速度)除以处理器数量的值。如果问题规模变大,同时处理器数量也随之增加,那么性能是不会有损耗的。通过增加不同的因子,可伸缩性系统会保持相同的性能或是改进性能。

阿姆达尔定律

阿姆达尔定律是用于设计处理器与并行算法的广泛使用的定律。它阐释了可获取的最大的加速是由程序中的串行组件所决定的:,其中,1-P表示程序中的串行组件(非并行部分)。这意味着如果一个程序中90%的代码是可并行的,但有10%的代码要保持串行,那么可获取的最大的加速就是9,即便对于无限数量的处理器来说亦如此。

古斯塔夫定律

古斯塔夫定律基于如下假设:

▼ 在增加问题维度时,其串行部分保持不变。

▼ 在增加处理器数量时,每个处理器所处理的工作保持不变。

这表明S(P)=P-α(P-1),其中P是处理器数量,S是加速,α是任何并行进程中的非并行部分。它与阿姆达尔定律相反,阿姆达尔定律认为单个进程的执行时间是固定的,并且每个进程的并行执行时间会减少。因此,阿姆达尔定律基于固定问题规模这样一个假设;它假设一个问题的全部工作量并不会随着机器规模(即处理器数量)的变化而变化。古斯塔夫定律着重解决了阿姆达尔定律的缺陷,后者并未将解决任务时所涉及的全部计算资源量考虑在内。它表明,设定并行问题解决方案时间的最佳方式是将所有计算资源都考虑进来,基于这一点,它解决了阿姆达尔定律的问题。Python简介

Python是一门强大、动态的解释型编程语言,广泛使用在各种应用中。其主要特性如下所示:

▼ 具有清晰且可读性好的语法。

▼ 具有大量标准库,通过额外的软件模块,我们可以增加数据类型、函数与对象。

▼ 易于学习的快速开发与调试能力;Python代码的开发速度最快,可以达到C/C++的10倍以上。

▼ 具有基于异常的错误处理。

▼ 具有强大的内省功能。

▼ 具有丰富的文档与软件社区。

可以将Python看作一门胶水语言。借助Python,我们可以开发出更好的应用,因为不同类型的程序员可以在一个项目上协作。比如,在构建科学应用时,C/C++程序员可以实现高效的数值算法,而同一项目的科学家则可以编写Python程序来测试并使用这些算法。科学家无须学习底层的编程语言,C/C++程序员也无须了解这里面涉及的科学内容。

可 以 通 过 https://www.python.org/doc/essays/omg-darpa-mcc-position了解更多关于这方面的内容。

准备工作

可以从https://www.python.org/downloads/下载Python。

虽然可以通过Notepad或是TextEdit来编写Python程序,但你会发现使用集成开发环境(IDE)来阅读和编写代码会更加轻松。

有很多专门针对Python设计的IDE,如IDLE(http://www.python.org/idle)、PyCharm (https://www.jetbrains.com/pycharm/)及Sublime Text(http://www.sublimetext.com/)等。

具体实现

下面来看一些非常简单的代码示例以了解Python的特性。记住,符号>>>表示Python shell。

▼ 整型操作:

我们只对这第一个示例展示出代码在Python shell中的样子,如下图所示。

下面来看看其他基本示例。

▼ 复杂数字:

▼ 字符串操作:

▼ 定义列表:

▼ while循环:

2

3

5

8

▼ if命令:

首先使用input()语句插入一个整数:

>>>x=int(input(〝Please enter an integer here:〝)) Please enter an integer here:

然后对插入的数字实现if条件:

▼ for循环:

▼ 定义函数:

▼ 导入模块:

试读结束[说明:试读内容隐藏了图片]

下载完整电子书


相关推荐

最新文章


© 2020 txtepub下载