导读:面向应用工程师的商业软件咨询、自研软件定制开发服务的仿真公众号,点击关注进入菜单,查看更多精彩内容。
(三)如何实现多核并行计算呢?
了解了多核、多Machine、多Rack后,我可以看一下软件(程序)是如何对这些资源进行调度使用的呢?
1、先看看CAE软件的设置
CAE软件现在基本上都都号称是支持多核并行计算的,可以通过界面、求解命令等设置使用的核数,这样我们拿ansys看一下。
在Ansys中进行并行计算或多核计算设置,可在Ansys的Product Launcher启动界面的High Performance Computing Setup页面中进行设置。
在此高级设置页面中可以设置并行计算多台多核,也可以设置单台多核设置,单台多核可以理解为单CPU多个内核。对于核数设置很多软件都有个限制,不过据说有些国产CAE软件是不限制核数的,如果有兴趣购买国产CAE软件的可以联系本公众号。言归正传咱们看看程序怎么实现的。
2、并行计算简介
并行平台的通信模型: 共享数据(POSIX、windows线程、OpenMP)、消息交换(MPI、PVM)。
并行算法模型: 数据并行模型、任务依赖图模型、工作池模型、管理者-工作者模型、消费者模型对于并行计算一个任务可能涉及到的问题:任务分解、任务依赖关系、任务粒度分配、并发度、任务交互并行算法性能的常见度量值:并行开销、加速比、效率(加速比/CPU数)、成本(并行运行时间*CPU数)
上面的描述可能不是特别好理解,我们下面通过实例来说明下。
演示中主要完成的工作在Sum0函数(工作本身没有什么意义,主要是消耗一些时间来代表需要做的工作:),然后分别用OpenMP工具(vc和icc编译器支持)和一个自己手工写的线程工具来并行化该函数,来看看多核优化后的效果;我测试用的编译器是vc2005;CPU是双核的AMD64x2 4200+(2.37G);内存2G双通道DDR2 677MHz;(分为三类:纯代码执行、OpenMP并行计算、自己手写多线程)
3、不用并行的纯代码
#include #include #include #include //一个简单的耗时任务double Sum0(double* data,long data_count);int main(){long data_count=200000;double* data=new double[data_count];long i;//初始化测试数据for (i=0;idata[i]=(double)(rand()*(1.0/RAND_MAX));const long test_count=200*2;//为了能够测量出代码执行的时间,让函数执行多次double sumresult=0;double runtime=(double)clock();for( i=0; i{sumresult+=Sum0(data,data_count);}runtime=((double)clock()-runtime)/CLOCKS_PER_SEC;printf ("< Sum0 > ");printf (" 最后结果 = %10.4f ",sumresult);printf (" 执行时间(秒) = %f ",runtime);delete [] data;return 0;}double Sum0(double* data,long data_count){double result=0;for (long i=0;i{data[i]=(double)sin(cos(data[i]));result+=data[i];}return result;}
看一下输出结果
< Sum0 >最后结果 = 55590743.4039执行时间(秒) = 6.156000
4、使用OpenMP并行计算
OpenMP是基于编译器命令的并行编程标准,使用的共享数据模型,现在可以用在C/C++、Fortan中;OpenMP命令提供了对并发、同步、数据读写的支持;(需要在项目属性中打开多线程和OpenMP支持,并要在多核CPU上执行才可以看到多CPU并行的优势)
OpenMP的实现如下:
#include #include #include #include //需要在项目属性中打开多线程和OpenMP支持#include //用OpenMP实现double Sum_OpenMP(double* data,long data_count);int main(){long data_count=200000;double* data=new double[data_count];long i;//初始化测试数据for (i=0;idata[i]=(double)(rand()*(1.0/RAND_MAX));const long test_count=200*2;//为了能够测量出代码执行的时间,让函数执行多次double sumresult=0;double runtime=(double)clock();for( i=0; i{sumresult+=Sum_OpenMP(data,data_count);}runtime=((double)clock()-runtime)/CLOCKS_PER_SEC;printf ("< Sum_OpenMP > ");printf (" 最后结果 = %10.4f ",sumresult);printf (" 执行时间(秒) = %f ",runtime);delete [] data;return 0;}double Sum_OpenMP(double* data,long data_count){double result=0;#pragma omp parallel for schedule(static) reduction(+: result)for (long i=0;i{data[i]=(double)sin(cos(data[i]));result+=data[i];}return result;}
Sum_OpenMP函数相对于Sum0函数只是增加了一句"#pragma omp parallel for schedule(static) reduction(+: result)" ; 它告诉编译器并行化下面的for循环,并将多个result变量值用+合并;(更多的OpenMP语法请参阅相关资料);
程序运行输出如下:
< Sum_OpenMP >最后结果 = 55590743.4039执行时间(秒) = 3.078000
5、利用多线程来并行化
使用了自定义的CWorkThreadPool多线程工具,此处不贴这部分代码了。需要在项目属性中打开多线程支持;多线程并行实现如下:
#include #include #include #include #include #include "WorkThreadPool.h" //使用CWorkThreadPool类double Sum_WorkThreadPool(double* data,long data_count);int main(){long data_count=200000;double* data=new double[data_count];long i;//初始化测试数据for (i=0;idata[i]=(double)(rand()*(1.0/RAND_MAX));const long test_count=200*2;//为了能够测量出代码执行的时间,让函数执行多次double sumresult=0;double runtime=(double)clock();for( i=0; i{sumresult+=Sum_WorkThreadPool(data,data_count);}runtime=((double)clock()-runtime)/CLOCKS_PER_SEC;printf ("< Sum_WorkThreadPool > ");printf (" 最后结果 = %10.4f ",sumresult);printf (" 执行时间(秒) = %f ",runtime);delete [] data;return 0;}double Sum0(double* data,long data_count){double result=0;for (long i=0;i{data[i]=(double)sin(cos(data[i]));result+=data[i];}return result;}struct TWorkData{double* part_data;long part_data_count;double result;};void sum_callback(TWorkData* wd){wd->result=Sum0(wd->part_data,wd->part_data_count);}double Sum_WorkThreadPool(double* data,long data_count){long work_count=CWorkThreadPool::best_work_count();std::vector work_list(work_count);std::vector pwork_list(work_count);long i;//给线程分配任务long part_data_count=data_count/work_count;for (i=0;i{work_list[i].part_data=&data[part_data_count*i];work_list[i].part_data_count=part_data_count;}work_list[work_count-1].part_data_count=data_count-part_data_count*(work_count-1);for (i=0;ipwork_list[i]=&work_list[i];//利用多个线程执行任务 阻塞方式的调用CWorkThreadPool::work_execute((TThreadCallBack)sum_callback,(void**)&pwork_list[0],pwork_list.size());double result=0;for (i=0;iresult+=work_list[i].result;return result;}
用多线程来把代码并行化从而利用多个CPU核的计算能力,这种方式具有比OpenMP更好的灵活性;但容易看出这种方式没有OpenMP的实现简便;Sum_WorkThreadPool函数更多的代码在处理将计算任务分解成多个独立任务,然后将这些任务交给CWorkThreadPool执行;程序执行输出如下:
< Sum_WorkThreadPool >最后结果 = 55590743.4039执行时间(秒) = 3.063000
总结:通过三天对于多核并行的讲解,相信大家一定有了深刻的认识,希望能够帮助大家更好的理解CAE软件并行计算原理,更希望大家能够分享、转发、再看等方式传递我们的信息给更多的朋友,谢谢。
欢迎大家转发,并点击下面的“在看”按钮,邀请更多的朋友一块讨论仿真技术,谢谢!