串行&并行程序在效率上的简单比较
开头:
这是老师在开学时布置的一道作业题目,完整问题为:“多核与单核的CPU在运行串行、并行程序时时,在效率上的差别。” 以前虽然常常听说多核,但是却没有去多了解下并行程序和串行程序,这次作业也算对这些概念有些熟悉。
多核兴起:
20世纪40年代第一台电子管计算机的发明标志计算工具进入了一个崭新的时代。20世纪50年代末,晶体管的发现促成了计算机技术的一大飞跃,其性能先之电子管计算机数十倍到数百倍以上。而之后集成电路时代的到来又为计算机的发展开辟了一个新的里程。
20世纪70年代,集成电路技术能够将计算机的控制单元和算术逻辑单元集成到一个芯片上制成了微处理器芯片。之后,对计算机性能的提升研究主要集中在对微处理器的性能的提高。传统的提高处理器性能的方法主要有三种途径: (一)提高主频;(二)优化技术,采用功能更强大的指令,流水处理的技术;(三)增加Cache的容量。
近年来,通过提高处理器主频来提升处理器性能的方法已经不大可能有大的进展,因而业界对处理器性能的提升逐渐转向了超线程、多核、缓存等技术。其中,多核是目前一个热点。
并行编程:
在以往,硬件技术的提升不会对运行其上的软件带来影响,而多核的诞生为其提出的一些难题。之前的应用程序都是针对一个运算核心而设计,而当处理器的架构升级为多核时,以前的应用程序便不能够充分地利用多核的优势,甚至会造成性能的下降。因而,随着多核渐行,今后程序员所将面临的挑战便是开发针对多核的并行程序。
并行程序的设计目标是挖掘问题求解过程中的并行性,寻求并行算法与并行机器体系结构的最佳匹配和映射,合理组织并行任务,减少额外消息传递和数据移动开销。目前,开发一个并行程序可以有三种途径,一个途径是串行程序自动并行化。第二条途径是设计全新的并行程序设计语言。第三条途径就是串行语言加并行库或伪注释指导语句的扩展,实际上就是增加一个库或一些新的制导语句来帮助进行消息传递和并行。这正是MPI和OpenMP所采取的途径。目前也是比较容易被接受且性能高的途径。但其程序开发效率很低,难度也比较大。
需要注意的是,并行执行的程序实际上没这么简单。在工作的切割、结合上,也是要多花时间的,所以在现实中,即使最佳状况,双核心的效能也不会是 1 + 1 = 2 这样的理想化。除此之外,也不是所有工作都是可以切割的!很多工作是有关联性的,这样如果直接切割给不同的处理核心各自去平行运算,出来的结果是肯定有问题的。而且,多执行绪的程式在编写、维护上,也都比单一执行绪的程式复杂上不少。
问题回答:
串行程序主要是针对具有单核体系架构的处理器而言的;而并行程序概念兴起于多核处理器诞生之后,为解决大规模问题而提出的并行计算。对于本题而言,给出的结论为:
·· 对于串行程序,因为程序经过编译过后没有并行区域,也即不能够派生到多个核当中运行,因此其在单核和多核上运行的时间是等同的。
·· 对于并行程序,在多核与单核上运行的效率往往与程序所要解决的问题的规模有关。并行计算追求的理想情况是采用P个处理器就能得到P陪速度的提升,但这很难达到,因为并行计算会引入一些额外的开销,也即前面提到的并行程序并不是简单的“切割”到多个处理器(多核)中去。总之,对于一些小规模的问题,并行程序的效果通常不如串行程序;对于一些较大规模的问题,并行程序的效果要远远好于串行程序,但是不能达到所期望的理想情况。以下是一些例证:
(1)一个简单的没有数据依赖及竞争条件的循环程序
- #include <iostream>
- #include <ctime>
- #include <omp.h>
- using namespace std;
- typedef long int l_int;
- int main(void)
- {
- l_int i = 0;
- l_int temp = 0;
- clock_t t1 = clock(); // time measuring
- {
- #pragma omp parallel for
- for(i=0; i<10000000; i++)
- {
- temp ++;
- }
- }
- clock_t t2 = clock(); // time measuring
- cout <<"time = " <<t2-t1 <<"/n";
- return 0;
- }
经过测试,在没有启用 OpenMP支持时,整个时间耗费为 0.031s. 在启用 OpenMP后,程序中的for循环被并行执行,然而其时间耗费却增加了近一倍,为 0.078s. 处理器加速比:
串行时间耗费/并行时间耗费 = 0.39
(2)一个较为复杂一些的程序, 计算Pi值:
- #include <iostream>
- #include <ctime>
- int main(void)
- {
- int i, stepNum;
- double sum, x, stepInterval;
- x = 0.0;
- sum = 0.0;
- stepNum = 10000000;
- stepInterval = 1.0/stepNum;
- clock_t t1 = clock(); // time measure
- {
- #pragma omp parallel for private(x),reduction(+:sum)
- for(i=0; i<stepNum; i++)
- {
- x = (i+0.5) * stepInterval;
- sum += 4.0 / (1+x*x);
- }
- }
- clock_t t2 = clock(); // time measure
- std::cout<<"Pi = " << sum*stepInterval <<"/n";
- std::cout <<"time = " <<(double)(t2-t1)/CLOCKS_PER_SEC <<"/n";
- return 0;
- }
经测试,在没有启用 OpenMP时,时间耗费为 0.125。启用OpenMP之后, 时间耗费为 0.062。 处理器加速比:
串行时间耗费/并行时间耗费 = 2.0
综上实验所述,能够说明之前提出的结论。