php 数字相加-老慧才网后记Linux、FORTRAN、MPI、IDL/GDL、LaTeX等

2023-09-05 0 3,100 百度已收录

介绍

并行编程曾经是顶级程序员在被小的超估计问题困扰时唯一关注的焦点。 而且,随着主流应用的多核处理器的出现php 数字相加并行编程现在已成为所有专业软件开发人员必须理解和掌握的技术。

并行编程可能很难,但事实上,它只是“不同”,而不是“难”。 它具有更传统的串行编程的所有功能,但还包括三个附加的、定义良好的步骤:

上述每一步都很重要。 最近一本关于并行编程设计模式的书 [mattson05] 详细介绍了前两个步骤。 本文将重点讨论第三步:在源代码中使用并行编程符号来表示并行算法。 这些符号可以是并行编程语言、通过库接口实现的应用程序编程接口(API)或添加到现有顺序语言的语言扩展。

选择一种奇特的并行编程符号并不是一件容易的事。 学习这种符号的难度可能有所不同,并且可能需要很长时间。 因此,掌握多个符号并选择一个使用是不切实际的。 程序员需要的是一种快速的方法来足够详细地了解不同符号的“优点”以及中间功能,以便就学习哪个符号做出明智的决定。

在本文中,我们将对几种不同的并行编程符号进行高级概述,重点介绍它们的主要使用方式,并揭示它们的特定优点和缺陷。 我们将详细介绍以下符号:

为了使我们的讨论尽可能具体和详尽,我们在每种情况下都部署了著名的 π 程序的并行版本。 这是借助矩形法则(选择被积函数和积分极限)进行的简单数值积分,因此从物理上来说,正确答案是 π。 很多人将其视为并行编程中最基本的程序。 在本文的最后,我们将简要介绍如何选择并行编程符号来使用和掌握。

π 程序:并行矩形积分

在微积分的学习中,我们了解到积分可以用几何形式表示为曲线下的面积。 也就是说,可以通过估计得到积分的近似值。 我们首先将积分区域定义为多个台阶,并在每个台阶内画一个圆,并使其高度等于台阶中心的被积函数值。 这样,每个正方形的面积之和就近似等于积分。

如图 1 所示: 矩形积分 - 每个条形都有一个固定长度的“台阶”。 每个条纹的高度就是被积函数的值。 将所有条纹的面积加在一起即可得出曲线下面积的近似值,即被积函数的值。

我们可以选择一个被积函数和积分极限,使得积分在数值上等于 π。 这样就可以更加直接地检查程序的正确性。 下面我们向您展示一个实现该算法的简单 C 程序:

static long num_steps = 100000;

double step;

void main ()

{                 int i;                  double x, pi, sum = 0.0;

 

                 step = 1.0/(double) num_steps;

 

                 for (i=0;i<= num_steps; i++){

                                  x = (i+0.5)*step;

                                  sum = sum + 4.0/(1.0+x*x);

                 }

                 pi = step * sum;

}

开放MP

OpenMP [omp] 是一种行业标准 API,用于为共享内存计算机编写并行应用程序。 OpenMP 的主要目的是使高性能计算中常见的面向循环的程序更易于编写。 OpenMP 中的各种结构支持 SPMD、Masterworker、管道和大多数其他并行算法 [Mattson05]。

OpenMP 是一种非常成功的并行语言。 市场上的每台共享内存计算机都支持其操作。 最近,Intel 在 OpenMP 上创建了一个变体,支持集群。 OpenMP可以逐步添加并行编程方法,因此现有的串行程序可以开发为并行程序。 而且,这个优点也成为了OpenMP最大的缺陷。 这是因为,通过使用渐进式并行性,程序员可能会错过程序的大量重建,从而失去最大化性能的机会。

OpenMP 标准仍在不断发展。 因此,一个名为 OpenMP 架构审查委员会的行业组织定期召开会议,开发该语言的新扩展。 OpenMP 的下一版本(版本 3.0)将包含任务排队功能。 这将使 OpenMP 能够处理更广泛的控制结构,以及更常见的递归算法。

OpenMP 简介

OpenMP 是基于fork-join 编程模型设计的。 OpenMP 程序最初以单线程方式开始运行。 如果程序员想要利用程序中的并行性,则需要分叉额外的线程来创建线程组。 此类线程在称为“并行区域”的代码区域内并行执行。 在并行区域的末尾,它等待所有线程完成其工作并将它们重新连接在一起。 此时,初始或“主”线程将继续执行,直到遇到下一个并行区域(或程序结束)。

OpenMP 的语言结构是根据编译器指令定义的,它将任务分配给编译器以实现理想的并行性。 在C和C++中,此类指令是根据引导语句来定义的。

php 数字相加-老慧才网后记Linux、FORTRAN、MPI、IDL/GDL、LaTeX等

OpenMP 指导语句在任何情况下都具有相同的行为方式

#pragmaompconstruct_nameone_or_more_clauses

其中,“construct_name”指定程序员想要执行的并行动作,“clauses”则改变动作,或者控制线程看到的数据环境。

OpenMP 是一种显式并行编程语言。 创建线程或将工作映射到该线程后,程序员必须指定他希望执行的操作。 因此,即使像 OpenMP 这样简单的 API 仍然有许多结构和谓词需要程序员学习。 幸运的是,只需使用整个 OpenMP 语言的一小部分即可完成许多工作。

可以借助“并行”结构在 OpenMP 中创建线程。

#pragma omp parallel

{

…. A block of statements

}

单独使用时(不改变任何谓词),程序可以创建一系列线程供运行时环境选择(此类线程一般等于处理器或核心的数量)。 每个线程都会根据并行引导语句执行语句块。 该块可以是 C 语言中任何合法的句子组,但有一个例外:不能分支到并行块内或之外。 你只需要思考片刻就可以理解。 如果一个线程要完整执行一组句子,但程序的后续行为也有意义,那么您不能只将线程分支到并行区域内的构造中或从并行区域内的构造中分支出来。 这是 OpenMP 的公共约束。 我们将这些缺乏单独分支的句子块称为“结构块”。

您可以让所有线程执行同一个句子,从而进行大量并行编程。 而要体验 OpenMP 的全部功能,我们要做的还不止这些。 我们需要在多个线程之间分担执行语句集的工作。 我们将这些方法称为“工作共享”。最常见的工作共享结构是循环结构,在 C 语言中称为“for”循环

#pragmaompfor

然而,这种结构仅适用于具有规范的简单循环

for(i=下限;i

“for”构造执行循环的迭代,并将它们打包到借助并行构造创建的初始线程组中。 循环限制和循环索引 (inc_exp) 的增量表达式需要在编译时完全确定,但此表示法中使用的任何常量必须在线程组之间保持相同。 你只需要思考一下就可以理解。 系统需要计算出循环的迭代次数,然后将其映射到可以分配给线程组的集合。 这只能以一致稳定的方式完成,前提是所有线程评估同一组索引。

请注意,“for”构造不会创建线程,您只能依靠并行构造来创建线程。 为了简洁起见,您可以将平行结构和“for”结构放在一个前导句子中。

#pragmaompparallelfor

这将创建一个线程组来执行紧随其后的循环迭代。

循环迭代必须是独立的,以便无论迭代的执行顺序如何,或者哪个线程执行循环的哪个迭代部分,循环结果都将相同。 如果一个线程写入一个变量,而另一个线程读取它,则会形成循环依赖关系,程序将生成不正确的结果。 程序员必须仔细剖析循环体,以确保不会发生循环传递依赖性。 在许多情况下,循环传递依赖项从保存中间结果(对于给定循环迭代)的变量开始。 在这种情况下,您可以通过声明每个线程都有自己的变量值来删除循环传递依赖性。 这是通过私有谓词实现的。 例如,如果循环使用名为“tmp”的变量来保存临时值,您可以将以下谓词添加到 OpenMP 结构中,以便可以在循环体内使用它php 数字相加,而不会导致任何循环传递依赖性。

私人(tmp)

另一种常见情况是变量出现在循环内部并用于累积每次迭代的值。 例如,您可以使用循环对所有估计结果求和以获得单个值。 这在并行编程中很常见,通常称为“归约”。在 OpenMP 中,我们的归约谓词表示为

减少(+:总和)

与私有谓词一样,可以将此谓词添加到 OpenMP 结构中以提示编译器等待规范。 此时,程序创建一个临时私有变量来估计每个线程累加操作的部分结果。 当结构运行到最后时,来自每个线程的值被组合起来形成最终的答案。 本规范中使用的操作也在谓词中指定。 在这些情况下,操作是“+”。 OpenMP 可以根据臭名昭著的物理操作的表征来定义私有变量值以进行规范。 例如,对于“+”,该值为零。

事实上,OpenMP 有更复杂的情况,使用这两个结构和两个子句,我们可以解释如何并行化 π 程序。

OpenMPπ 程序

为了简单起见,我们将所需的步骤标准化,但仅使用默认的线程数来工作。 在串行π程序中,还有一个单独的循环需要并行化。 除了因变量“x”和累积变量“sum”之外,该循环的迭代是完全独立的。 请注意,这里使用“x”来估计循环迭代内的临时存储空间。为此,我们可以通过私有谓词将变量定位到每个线程,以方便其处理

私人(x)

从技术上讲,循环控制索引创建了循环传递依赖性。 而且,OpenMP觉得循环控制索引需要位于每个线程中,这样就可以手动为所有线程私有化。

累积变量“sum”用于估计总和。 这是一个经典的归约,因此我们可以使用归约谓词:

减少(+:总和)

通过将这个谓词添加到“parallelfor”结构中,我们可以使用OpenMP来完成π程序的并行化。

#include

static long num_steps = 100000; double step;

void main ()

{                 int i;                  double x, pi, sum = 0.0;

                 step = 1.0/(double) num_steps;

#pragma omp parallel for private(x) reduction(+:sum)

                 for (i=0;i<= num_steps; i++){

                                  x = (i+0.5)*step;

                                  sum = sum + 4.0/(1.0+x*x);

                 }

                 pi = step * sum;

}

请注意,我们的 OpenMP 还包含标准包含文件

#包括

这指定了程序员有时需要的 OpenMP 类型和运行时库。 请注意,在此程序中,我们没有利用该语言的此功能,最好包含 OpenMPincludefile,以防以后需要更改程序。

MPI

MPI(消息传递接口)是我们当今使用的最古老的并行编程 API 之一。 MPI程序作为一系列独立的进程,主要通过发送和接收消息进行交互。 MPI 的一大优点是它只占用并行计算机中硬件的一小部分。 它唯一的要求是处理器或核心共享相同的简单网络,从而在任何进程组之间完全路由消息。 这还支持 MPI 在任何通用并行系统上运行 - 无论是从对称多处理器到分布式内存,还是从大规模并行超级计算机到各种集群。

MPI 诞生于 20 世纪 90 年代初,当时集群刚刚流行,大规模并行处理器 (MPP) 主导了高性能计算。 每个 MPP 制造商都有自己的消息符号。 厂商很高兴看到这种情况,因为这样可以将用户锁定在自己的产品线中,但是却让程序员很困扰。 因为软件的寿命比硬件的寿命要长得多。 此外,由于没有可移植性符号,每次开发新笔记本时,应用程序程序员都必须花费大量精力将其软件从一种消息符号转换为另一种消息符号。

MPI 不是第一个便携式消息传递库,但它是第一个由行业/国家实验室/学术界联合创建的库。 MPI的创建过程几乎聚集了业界所有的主要力量,它很快成为高性能估算领域的标准消息传递接口。 时至今日,MPI已经走过了大约15年的时间,但它仍然是高性能计算领域并行编程应用中最常用的符号。

目前,大多数MPI程序使用单程序多数据或SPMD模式[mattson05]。 它的原理非常简单:所有处理单元(PE)运行相同的程序。 它们都有一个奇特的整数 ID,用于确定它们在处理单元集中的顺序。 这样,程序就可以利用排序来分配工作,并决定由哪个PE来处理哪部分工作。 也就是说,只有一个程序,由于根据ID有多种选择,所以PE之间的数据也可能不同; 即“单程序、多数据”模式。

MPI 概述

MPI是一种可靠实用的消息传递系统,旨在支持广泛的硬件,并能以完整的模块化设计支持复杂的软件架构。

MPI的设计理念是基于通信器的。 他们可以在创建一组流程时定义组。 这样进程组就可以共享通信环境,从而更好地通信。 这些进程组和通信环境的组合可以定义一个奇特的通信器。 当您考虑在程序中使用库时,这个概念的力量就会凸显出来。 如果程序员不小心,库开发人员创建的信息可能会干扰程序中调用库的信息。 而借助通信器,图书馆开发人员可以创建自己的通信环境,并确保图书馆内部活动不会干扰通过系统传递的相关信息。

当 MPI 程序启动时,会创建一个默认通信器 MPI_COMM_WORLD。 通信器作为第一个参数传递给每个 MPI 类库。 其他参数用于定义信息的来源,以及定义存储信息的缓冲区。 在这些情况下,MPI 类库将返回一个整数值作为错误参数,以查询类库执行过程中出现的任何问题。

MPI程序一般将三个类库的调用设置在接近开头的位置,从而设定MPI的使用方式。

intmy_id,numprocs;

MPI_Init(&argc,&argv);

MPI_Comm_Rank(MPI_COMM_WORLD,&my_id);

MPI_Comm_Size(MPI_COMM_WORLD,&numprocs);

第一个类库(MPI_Init)用于输入任何 C 程序员都熟悉的命令行参数并初始化 MPI 环境。 后两个类库用于输入MPI通信器(本例中为默认通信器),返回调用进程排名(rank)和进程总数。 这里,排序作为进程的唯一标识,可以从0到进程数乘1进行排序。

有关要创建多少个进程以及它们将在哪个处理器上运行的详细信息保留在 MPI 应用程序编程套接字之外。 由于不同平台支持MPI,所以需要使用不同的方法。 在大多数情况下,会有一个主机文件按名称顺序列出每个处理器的信息。 该信息被传递到大多数 MPI 平台上可用的通用 shell 脚本(称为 mpirun)以启动 MPI 程序。 由于这个简单过程的细节在不同平台上的表现有所不同,因此我们不会在这里讨论它们。

在每个 MPI 程序结束时都应该有一个解释器来关闭环境。 该函数返回一个整数值错误代码。

intMPI_Finalize();

MPI 程序在此类解释器之间运行。 大多数程序由常规串行代码组成,以您选择的语言表示。 如上所述,即使每个进程执行相同的代码,程序行为也会根据进程的顺序而有所不同。 此外,在进程之间需要通信或其他交互的地方,还会插入 MPI 类库。 MPI的第一个版本有120多个类库,最新版本(MPI2.0)有更多的类库。 此外,大多数程序仅使用 MPI 函数的一小部分。 为此,我们将只讨论一个方案; 执行归约并将最终归约结果返回给组中每个进程的类库。

intMPI_Reduce(void*sendbuf,void*recvbuf,

intcount,MPI_Datatype数据类型,MPI_OPop,

introot,MPI_COMMcomm。)

该函数获取缓冲区“sendbuf”中“datatype”类型的“count”值,并使用“op”操作来累加每个进程的结果,最后将结果放入排序为“的进程的“recvbuf”缓冲区中根” 。 这样MPI_Datatype和MPI_OP就可以直观的得到想要的值,比如“MPI_DOUBLE”或者“MPI_SUM”。

据悉,使用MPI广播消息(MPI_Bcast)中的其他常用类库,还可以定义“屏障”同步点(MPI_Barrier)、发送消息(MPI_Send)或接收消息(MPI_Recv)。 您可以通过在线表格或 [mpi] 和 [mattson05] 了解有关 MPI 的更多信息。

MPIπ计划

MPIπ程序是对原始串行代码的直接更改。 为了简单起见,我们将继续在程序本身中设置步骤数,而不是输入一个值然后将其广播到其他进程。

程序从MPIinclude文件开始,定义MPI中的数据类型、常量和各种类库。 之后,我们添加了标准的3个类库来初始化MPI环境并在程序中使用基本参数(进程的数量和顺序)。

#include

static long num_steps = 100000;

void main (int argc, char *argv[])

{

                 int i, my_id, numprocs;

     double x, pi, step, sum = 0.0 ;

                 step = 1.0/(double) num_steps ;

                   MPI_Init(&argc, &argv) ;

                 MPI_Comm_Rank(MPI_COMM_WORLD, &my_id) ;

                 MPI_Comm_Size(MPI_COMM_WORLD, &numprocs) ;

                 my_steps = num_steps/numprocs ;

                 for (i=my_id; i<num_steps; i+numprocs)

                 {

                                  x = (i+0.5)*step;

                                  sum += 4.0/(1.0+x*x);

                 }

                 sum *= step ;

                 MPI_Reduce(&sum, &pi, 1, MPI_DOUBLE, MPI_SUM, 0,

                                   MPI_COMM_WORLD) ;

     MPI_Finalize(ierr);

 

}

最后,我们借助通用方法来界定进程集合中的循环迭代。 请注意,这里的循环限制已更改,从运行每个进程 ID,更改为随着组中进程数量的增加而增加的迭代次数。 这是因为 MPI 中定义的排序用作 ID,排序号可以从 0 到进程数乘以 1。本质上,这些简单的转换在循环中分配循环迭代,就像我们分配一副扑克牌一样到不同的进程。

每个过程完成后,部分求和会将得到的部分结果放入变量“sum”中。 这些规范主要包含在以下调用中:

MPI_Reduce(&sum,&pi,1,MPI_DOUBLE,MPI_SUM,0,

MPI_COMM_WORLD);

相比我们上面讨论的MPI_Reduce的定义,这里各个参数的含义应该更加清晰。 在我们使用的部分和中,“sum”是发送缓冲区,变量“pi”是接收缓冲区。 根据MPI_Reduce类库的第六个参数,该值到达进程时将排序为“0”。 “发送缓冲区”包含带有附加累加操作(即 MPI_SUM)的 MPI_DOUBLE 类型的值。 最后,参与该协议操作的进程将使用通信器 MPI_COMM_WORLD 进行操作。

Java 线程概述

Java 语言从设计之初就具有外部多线程支持。 作为Java技术的重要组成部分,线程只能在语言(句型)层面、Java虚拟机、类库(classlibrary)层面上得到支持。 Java 线程与 POSIXpthreads 有许多相似之处。 Java类库提供的线程类可以支持丰富的方法来启动、运行或停止线程,以及检测线程的状态。

Java 的线程支持包括一组基于监视器和条件变量的复杂同步谓词。 在语言级别,声明为同步的类库或代码块中的方法不会同时运行。 这些技巧或模块在监视器的控制下运行,这有助于确保以这种方式或模块访问的数据始终处于一致的状态。 所有Java对象都有自己的监视器,这些监视器通常在第一次使用时由JVM显示和激活。 监视器的作用与pthread中定义的条件变量对和互斥体非常相似。 与 forpthread 不同的是,Java 线程在等待状态时可能会被中断。 当它正在等待风暴通知或在 I/O 调用期间被拦截时,通常会发生这种情况。

Java线程π程序

在这个简单的示例中,我们展示了如何使用“简单”Java 线程编写 π 程序的并行版本:

public class PI1 {

                 static long num_steps = 100000;

                 static double step;

                 static double sum = 0.0;

                 static int part_step;

 

static class PITask extends Thread {

                                  int part_number;

                                  double x = 0.0;

                                  double sum = 0.0;

                                  public PITask(int part_number) {

                                                   this.part_number = part_number;

                                  }

                                  public void run() {

                                                   for (int i = part_number; i < num_steps; i += part_step) {

                                                                    x = (i + 0.5) * step;

                                                                    sum += 4.0 / (1.0 + x * x);

                                                   }

                                  }

 

                 }

                 public static void main(String[] args) {

                                  int i;

                                  double pi;

                                  step = 1.0 / (double) num_steps;

                                  part_step = Runtime.getRuntime().availableProcessors();

                                  PITask[] part_sums = new PITask[part_step];

                                  for (i = 0; i < part_step; i++) {

                                                   (part_sums[i] = new PITask(i)).start();

                                  }

                                  for (i = 0; i < part_step; i++) {

                                                   try {

                                                                    part_sums[i].join();

                                                   } catch (InterruptedException e) {

                                                   }

                                                   sum += part_sums[i].sum;

                                  }

                                  pi = step * sum;

                                  System.out.println(pi);

                 }

}

在Java中启动一个新线程时,我们通常会对Thread类进行细分,并定义自定义的run()方法,以保证主要工作能够并行完成。 在我们的示例中,这项工作可以在 PITask 类的 run() 方法中执行。 出于性能原因,整个集成范围被分为part_step片段,此类片段的数量等于可用处理器的数量。 PITask 对象由一个part_number 参数化(代表积分范围内的一个段); 因此,run() 的主体估计所选积分范围内的部分和。 当调用start()方法时,实际的线程同时启动并并发执行。 我们可以在所有依赖范围的循环中执行此操作。 之后,我们运行第二个循环,通过调用其 join() 方法等待每个生成线程的完成,然后汇总每个线程的结果。 在此示例中,每个整数范围都显式映射到单独的 Java 线程。

在这个例子中,我们显式地创建了Java线程,因此我们必须将集成范围划分为多个部分,以方便线程之间工作的自动划分。 事实上,这被认为是非常冗长的,如果我们不使用这些技术并创建与积分范围内的步骤一样多的线程,我们会发现程序的性能是不可接受的。 这是因为,一般来说,创建 Java 线程是一个非常昂贵的项目。

Java并发模型FJTask框架

上面提到的“简单”Java线程只是Java多线程支持的最低级别; 有许多更高级别的线程库可以提高 Java 多线程功能的基础级别并为一些常见任务添加解决方案。 从 Java 标准 1.5 开始就可用的 java.util.concurrent 包就是一个值得我们关注的例子。 该包包括对基本 Java 线程的许多增强,例如线程池支持、原子变量和复杂的同步子句。 此外,util.concurrent 包的某些片段不兼容 J2SE,因此它仍然只能作为独立库(称为 EDU.oswego.cs.dl.util.concurrent)使用。 其中最重要的缺失部分是 FJTask 框架,它使用 Java 的 fork-join 并行性概念来并行化估计密集型估计,例如定积分或矩阵加法运算。 FJTask是Thread的简单直接模拟。 它一般指的是“基于任务”的并行性,而不是“基于线程”的并行性。 FJTasks 通常在同一个 Java 线程池上执行。 它还支持 Thread 类中许多最常见的方法,包括 start()、yield() 和 join()。

FJTask不支持一些Java线程方法,例如优先级控制。 因此,它的主要经济优势是不支持任何类型的拦截行动。 FJTask 中没有停止拦截的动机,并且非常短的等待/拦截效果特别好。 FJTask 不支持任意同步,因为一旦开始执行就无法暂停和恢复单个任务的执行。 FJTasks 在连续执行期间也应该有界,并且不应该包含无限循环。 FJTask 应顺利完成运行,不应需要等待或执行阻塞 IO。 因此,FJTask和Thread之间存在很大的成本差异。 至少在 JVM 上运行时,FJTask 可以比 Thread 快 2 到 3 个数量级,具有高性能的垃圾收集(所有 FJTask 很快就会变成垃圾)和良好的本地线程支持。

Java并发模型FJTaskpi程序

在下面的例子中,我们展示了如何借助FJTask框架来编译PI程序:

import EDU.oswego.cs.dl.util.concurrent.FJTask;
import EDU.oswego.cs.dl.util.concurrent.FJTaskRunnerGroup;

 

public class PI2 {

                 static int num_steps = 100000;

                 static double step;

                 static double sum = 0.0;

                 static int part_step;

                 static class PITask extends FJTask {

                                  int i = 0;

                                  double sum = 0.0;

                                  public PITask(int i) {

                                                   this.i = i;

                                  }

                                  public void run() {

                                                   double x = (i + 0.5) * step;

                                                   sum += 4.0 / (1.0 + x * x);

                                  }

                 }

 

                 public static void main(String[] args) {

                                  int i;

                                  double pi;

                                  step = 1.0 / (double) num_steps;

                                  try {

                                                   FJTaskRunnerGroup g = new FJTaskRunnerGroup(Runtime.getRuntime()

                                                                                     .availableProcessors());

                                                   PITask[] tasks = new PITask[num_steps];

                                                   for (i = 0; i < num_steps; i++) {

                                                                    tasks[i] = new PITask(i);

                                                   }

                                                   g.invoke(new FJTask.Par(tasks));

                                                   for (i = 0; i < num_steps; i++) {

                                                                    sum += tasks[i].sum;

                                                   }

                                                   pi = step * sum;

                                                   System.out.println(pi);

                                                   System.out.println(Math.PI);

                                  } catch (InterruptedException ie) {

                                  }

                 }

}

首先,与我们在前面的示例中所做的类似,我们为 PITask 类创建一个 run() 方法。 并且这样,PITask 只会计算第 i 步对应的单个值 x,而不是积分范围的部分和。 我们立即创建一个 PITask 对象数组,将其与 FJTask.Par 对象打包,并通过调用 FJTaskRunnerGroup 对象上的 invoke() 来执行它。 使用 FJtask.Par 对象进行打包指示框架在线程池上并行执行一组基本任务(我们已将此线程池中的线程数设置为处理器数)。 在数组中的所有任务完成之前,无法使用此示例中的 invoke() 方法。 这使我们能够立即根据每个特定任务获得的各个总和值来估计总数。

请注意:Javaπ 程序的这个小修订版没有显式创建任何线程,也没有定义线程和任务之间的任何工作分区。 然而,您可能已经注意到,尽管与上面我们在线程之间明确划分工作的示例相比,它仍然表现得非常好。 这是因为所有新的 FJtask 都可以异步创建和执行,几乎与调用方法一样快。 而且,为了获得最佳性能,我们始终建议您为每个 FJTask 对象分配适当的工作量,以确保这些对象的数量是可管理的。 这将有助于减轻 JVM 中垃圾收集器的压力。

选择并行编程符号

在这篇文章中,我们已经讨论了并行编程的许多常见符号。 本文中使用的程序非常简单——甚至可能太简单了。 并且,我们希望您可以从这样一个简单的示例中很好地理解所有并行编程符号。

此类并行编程表示法的复杂程度、改变时所需的原始串行程序数量以及一起使用时可能出现的错误各不相同。 鉴于您倾向于使用的并行算法类型的性质,建议您考虑所有这些激励因素。 据悉,您还需要考虑:

可移植性:您需要支持哪些平台? MPI 如此受欢迎的原因之一是它无处不在。 如果您只计划支持具有共享地址空间的硬件,那么基于线程的表示法实际上更合适。

性能:可管理的运行时和中级运行时环境大大减轻了程序员的负担。 由于创建和维护软件的成本较高,因此这一优势对您来说很重要。 而这种优势也是有代价的。 由于低级 API(例如 Windows Threads、Pthreads 或 MPI)中的硬件是直接针对程序员的,因此需要更详细的优化。 如果需要扩展所有可用核心,这种优化非常重要。

串行与并行产品发布:软件的生命周期很长。 成功的软件开发人员支持具有较长使用寿命的产品。 因此,将软件的串行和并行版本保留在一个源代码树中变得很重要。 如果并行编程符号需要大量重写软件以支持并行性,则这是很难做到的。

熟悉度:学习一门新语言很困难。 据悉,当多个开发人员学习一种不熟悉的语言时,成本也可能特别高。 因此,如果并行表示法是人们熟悉的串行语言的扩展,那就显得尤为重要。

测试:软件产品必须经过广泛的测试。 在专业的开发环境中,测试成本很容易超过最初创建软件的成本。 这就是增量策略在并行编程(一般使用OpenMP)中的优势。 通过增量并行,开发人员可以在每次添加结构时测试结果,并确保结果与原始串行代码保持一致。

参考

关于作者

TimMattson 是一位并行程序员。 在过去的 20 年里,他使用并行计算机来产生物理反应、重新组装蛋白质、寻找石油、分析基因以及解决许多其他科学问题。 当大多数应用程序员都在编写并行软件时,蒂姆还决心开发一种罕见的串行软件。 多年来,他一直坚信找到合适的并行编程环境是解决问题的关键。 他尝试了多种并行编程环境并创建了一些新的环境(包括 OpenMP)。 When it turned out that these techniques were not as effective as he had hoped, he quickly switched gears and decided to help us realize the need to understand the way professional programmers think about parallel programming before people were confused by the language and software tools. In order to solve this problem, Tim spent more than three years working with others to develop a design pattern language for parallel programming ("Parallel Programming Patterns", Addison Wesley Publishers, 2004). Tim currently works for Intel and continues to conduct research on parallel application programming issues in the Applied Research Laboratory of Intel's Enterprise Technology Group.

收藏 (0) 打赏

感谢您的支持,我会继续努力的!

打开微信/支付宝扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
点赞 (0)

悟空资源网 php php 数字相加-老慧才网后记Linux、FORTRAN、MPI、IDL/GDL、LaTeX等 https://www.wkzy.net/game/194040.html

常见问题

相关文章

官方客服团队

为您解决烦忧 - 24小时在线 专业服务