计算化学公社

标题: Fortran OpenMP求助 第二弹 [打印本页]

作者
Author:
wxhwbh    时间: 2020-6-12 16:14
标题: Fortran OpenMP求助 第二弹
本帖最后由 wxhwbh 于 2020-6-12 16:17 编辑

上次在这个贴子http://bbs.keinsci.com/thread-16841-1-1.html问了关于openmp的一些问题,学了不少关于私有/公有变量的知识,非常感谢大家。由于我的程序基本全部重写,发个新帖问一个我新遇到的问题。
还是一个动力学程序,对不同的轨迹做演化,大致如下:

  1. !$OMP PARALLEL DO private(...) shared(...)
  2. do itraj=1,Ntraj

  3.      call sub1 !抽样
  4.     call sub2(itraj) !演化
  5. end do
  6. !$OMP END PARALLRL DO
复制代码


变量的 private 和 shared已经正确声明过了,但程序还是不对。后来我发现了问题所在:一次循环里必须要在同一个线程内先抽样再演化,而我的程序却做不到这一点,比如如果改成下面这样测试:
  1. !$OMP PARALLEL DO private(...) shared(...)
  2. do itraj=1,Ntraj
  3. write(*,*) "itraj=",itraj
  4. call sub1 !抽样
  5. write(*,*) 1,itraj
  6. call sub2(itraj) !演化
  7. write(*,*) 2,itraj
  8. end do
  9. !$OMP END PARALLRL DO
复制代码

则输出结果就会为

  1. ...
  2. itraj=          47
  3.            1          47
  4.            2        1547
  5. itraj=        1548
  6.            1        1548
  7.            2         546
  8. itraj=         547
  9.            1         547
  10.            2        1048

  11. ...
复制代码

也就是同一次循环内两个子程序处的线程是不一样的。但我翻了一下OpenMP的手册,没有找到可以强制一个线程内循环顺序执行的命令。请问有什么方法可以解决吗?




作者
Author:
hebrewsnabla    时间: 2020-6-12 16:24
你这个需求非常适合用MPI解决,可以保证先抽样再演化。

使用openmp的时候最好使并行粒度小一点,例如itraj的循环搞成串行的,sub1和sub2的内部做并行。
作者
Author:
wxhwbh    时间: 2020-6-12 16:44
hebrewsnabla 发表于 2020-6-12 16:24
你这个需求非常适合用MPI解决,可以保证先抽样再演化。

使用openmp的时候最好使并行粒度小一点,例如itr ...

本身并行的目的就是减少轨迹数大的时候计算所需的时间。如果itraj串行了而里面的sub1和sub2并行的话对结果不会有更大的影响吗?
作者
Author:
hebrewsnabla    时间: 2020-6-12 17:43
本帖最后由 hebrewsnabla 于 2020-6-12 18:53 编辑
wxhwbh 发表于 2020-6-12 16:44
本身并行的目的就是减少轨迹数大的时候计算所需的时间。如果itraj串行了而里面的sub1和sub2并行的话对结 ...

怎么可能影响结果?结果不对肯定是程序没写对。我举个例子

  1. do i=1,N
  2.   ! 一堆事情
  3.   do j=1,M
  4.   ! 一大堆事
  5.     do k=1,P
  6.      !一大堆事情
  7.     enddo
  8.   enddo
  9. enddo
复制代码

请问把omp控制命令写在哪个do前面比较省事?当然是最内层循环了,这样需要处理的private/shared问题最少。
效率应该是差不多的。除非你的sub1,sub2里面没有循环,没法做并行。

当然,对这个问题MPI可能更省事。搞不定就用MPI

另外我觉得你的输出有的奇怪,难道不应该是各个线程先输出一批itraj,再输出一批1,itraj么,你这个看起来像是是串行的。
你要确认对于某个itraj,第一步和第二步是同一个线程,应该用OMP_GET_THREAD_NUM()输出线程号看看。




作者
Author:
wxhwbh    时间: 2020-6-12 18:52
hebrewsnabla 发表于 2020-6-12 17:43
怎么可能影响结果?结果不对肯定是程序没写对。我举个例子

请问把omp控制命令写在哪个do前面比较省事 ...

但是,sub2的循环是时间演化,也就是下一次循环需要上一次的信息。这样直接做并行能得到正确的结果吗?
作者
Author:
hebrewsnabla    时间: 2020-6-12 18:55
本帖最后由 hebrewsnabla 于 2020-6-12 18:57 编辑
wxhwbh 发表于 2020-6-12 18:52
但是,sub2的循环是时间演化,也就是下一次循环需要上一次的信息。这样直接做并行能得到正确的结果吗?

哦你是说sub2内部的循环是时间演化。那应该不能并行。

作者
Author:
hebrewsnabla    时间: 2020-6-12 18:59
wxhwbh 发表于 2020-6-12 18:52
但是,sub2的循环是时间演化,也就是下一次循环需要上一次的信息。这样直接做并行能得到正确的结果吗?

这个情况用MPI比较好。

非要用omp的话,我重复一下我的疑问:
  一楼贴的输出有点奇怪,难道不应该是各个线程先输出一批itraj,再输出一批1,itraj么,你这个看起来像是是串行的。
  你要确认对于某个itraj,第一步和第二步是同一个线程,应该用OMP_GET_THREAD_NUM()输出线程号看看。
作者
Author:
wxhwbh    时间: 2020-6-12 19:47
hebrewsnabla 发表于 2020-6-12 18:59
这个情况用MPI比较好。

非要用omp的话,我重复一下我的疑问:

我把程序改成这样输出:
  1. !$OMP PARALLEL DO private(...) shared(...)
  2. do itraj=1,Ntraj
  3. write(*,*) "itraj=",itraj,"id=",omp_get_thread_num()
  4. call sub1 !抽样
  5. write(*,*) "sample finishing,id=",omp_get_thread_num()
  6. call sub2(itraj) !演化
  7. write(*,*) "evolution finishing,id=",omp_get_thread_num()
  8. write(*,*) !空一行
  9. end do
  10. !$OMP END PARALLRL DO
复制代码
用了4个核并行,输出是这样的:
  1. ...
  2. itraj=         533 id=           1
  3. sample finishing,id=           1
  4. evolution finishing,id=           3

  5. itraj=        1532 id=           3
  6. sample finishing,id=           3
  7. evolution finishing,id=           2

  8. itraj=        1033 id=           2
  9. sample finishing,id=           2
  10. evolution finishing,id=           0

  11. itraj=          34 id=           0
  12. sample finishing,id=           0
  13. evolution finishing,id=           3
  14. ...
复制代码
确实采样和演化不在一个线程内。

作者
Author:
wxhwbh    时间: 2020-6-12 20:32
hebrewsnabla 发表于 2020-6-12 18:59
这个情况用MPI比较好。

非要用omp的话,我重复一下我的疑问:

再检查了一下,前几次的输出可能会有一点不一样,如下:

  1. itraj=           1 id=           0
  2. itraj=        1001 id=           2
  3. itraj=         501 id=           1
  4. sample finishing,id=           0
  5. sample finishing,id=           1
  6. itraj=        1501 id=           3
  7. sample finishing,id=           2
  8. sample finishing,id=           3
  9. evolution finishing,id=           0

  10. itraj=           2 id=           0
  11. sample finishing,id=           0
  12. evolution finishing,id=           3

  13. itraj=        1502 id=           3
  14. sample finishing,id=           3
  15. evolution finishing,id=           1

  16. ...
复制代码

作者
Author:
hebrewsnabla    时间: 2020-6-12 20:42
wxhwbh 发表于 2020-6-12 19:47
我把程序改成这样输出:
用了4个核并行,输出是这样的:
确实采样和演化不在一个线程内。

你这样还是不对,你要检查的是itraj和进程号是不是对应,每次输出进程号的时候要把itraj一起输出。
作者
Author:
wxhwbh    时间: 2020-6-12 21:21
hebrewsnabla 发表于 2020-6-12 20:42
你这样还是不对,你要检查的是itraj和进程号是不是对应,每次输出进程号的时候要把itraj一起输出。

比如这样
  1. itraj=        1501 id=           3
  2. itraj=           1 id=           0
  3. sample finishing,id=           0 itraj=           1
  4. itraj=        1001 id=           2
  5. sample finishing,id=           2 itraj=        1001
  6. itraj=         501 id=           1
  7. sample finishing,id=           1 itraj=         501
  8. sample finishing,id=           3 itraj=        1501
  9. evolution finishing,id=           0 itraj=           1
  10. itraj=           2 id=           0
  11. sample finishing,id=           0 itraj=           2
  12. evolution finishing,id=           1 itraj=         501
  13. evolution finishing,id=           3 itraj=        1501
  14. evolution finishing,id=           2 itraj=        1001
  15. evolution finishing,id=           0 itraj=           2
  16. ...
复制代码

确实不在一个线程里。
或者我还有一个想法,就是让线程在执行完sub1后暂停,待所有线程都执行完sub1后,再开始并行执行sub2。OpenMP有功能可以做到吗?
作者
Author:
hebrewsnabla    时间: 2020-6-12 21:31
wxhwbh 发表于 2020-6-12 21:21
比如这样

确实不在一个线程里。

这明明是对的啊
itraj=1时,id一直=0;itraj=1001, id=2; itraj=501, id=1; ...


作者
Author:
wxhwbh    时间: 2020-6-12 21:41
本帖最后由 wxhwbh 于 2020-6-12 21:46 编辑
hebrewsnabla 发表于 2020-6-12 21:31
这明明是对的啊
itraj=1时,id一直=0;itraj=1001, id=2; itraj=501, id=1; ...

原来如此,我发现了解决方法。把OMP PARALLRL DO 改成OMP PARALLRL DO ORDERED结果就对了。虽然没有定义ORDERED块,但好像DO ORDERED就能满足顺序执行。
但运行起来好慢啊....
作者
Author:
hebrewsnabla    时间: 2020-6-12 21:47
本帖最后由 hebrewsnabla 于 2020-6-12 21:49 编辑
wxhwbh 发表于 2020-6-12 21:41
原来如此,我发现了解决方法。把OMP PARALLRL DO 改成OMP PARALLRL DO ORDERED结果就对了。虽然没有定义O ...

不需要ordered,你上面输出的结果就是对的;不使用ordered也可以得到这个结果吧?

ordered等于没并行。


作者
Author:
wxhwbh    时间: 2020-6-12 22:07
hebrewsnabla 发表于 2020-6-12 21:47
不需要ordered,你上面输出的结果就是对的;不使用ordered也可以得到这个结果吧?

ordered等于没并行 ...

不行,不用ORDERED的结果不对,上面那个输出是带ORDERED的。不带的是这样
  1. itraj=           1 id=           0
  2. itraj=         501 id=           1
  3. sample finishing,id=           1 itraj=         501
  4. itraj=        1001 id=           2
  5. sample finishing,id=           2 itraj=        1001
  6. itraj=        1501 id=           3
  7. sample finishing,id=           3 itraj=        1501
  8. sample finishing,id=           0 itraj=           1
  9. evolution finishing,id=           3 itraj=        1501
  10. itraj=        1502 id=           3
  11. sample finishing,id=           3 itraj=        1502
  12. evolution finishing,id=           2 itraj=        1001
  13. itraj=        1002 id=           2
  14. sample finishing,id=           2 itraj=        1002
  15. evolution finishing,id=           0 itraj=           1
  16. itraj=           2 id=           0
  17. sample finishing,id=           0 itraj=           2
  18. evolution finishing,id=           1 itraj=         501
  19. itraj=         502 id=           1
  20. sample finishing,id=           1 itraj=         502
  21. evolution finishing,id=           3 itraj=        1502
  22. itraj=        1503 id=           3
  23. sample finishing,id=           3 itraj=        1503
  24. evolution finishing,id=           1 itraj=         502
  25. itraj=         503 id=           1
  26. sample finishing,id=           1 itraj=         503
  27. evolution finishing,id=           0 itraj=           2
  28. itraj=           3 id=           0
  29. sample finishing,id=           0 itraj=           3
  30. evolution finishing,id=           2 itraj=        1002
  31. itraj=        1003 id=           2
  32. sample finishing,id=           2 itraj=        1003
  33. evolution finishing,id=           3 itraj=        1503
  34. itraj=        1504 id=           3
  35. sample finishing,id=           3 itraj=        1504
  36. evolution finishing,id=           1 itraj=         503
  37. ...
复制代码

这样看好像又在一个线程内,但结果就是不对。
作者
Author:
hebrewsnabla    时间: 2020-6-12 22:08
wxhwbh 发表于 2020-6-12 22:07
不行,不用ORDERED的结果不对,上面那个输出是带ORDERED的。不带的是这样

这样看好像又在一个线程内, ...

是啊,就是在一个线程内。结果不对一定是你别的地方有问题。

用了ordered,等同于串行,结果当然对了。
作者
Author:
万里云    时间: 2020-6-13 11:05
同建议用MPI。

MPI默认变量全部为private,并且会严格按照用户指定的方式计算。
作者
Author:
wxhwbh    时间: 2020-6-13 16:12
hebrewsnabla 发表于 2020-6-12 22:08
是啊,就是在一个线程内。结果不对一定是你别的地方有问题。

用了ordered,等同于串行,结果当然对了 ...

我再检查了一下发现,可能是采样的问题。我在sub1内部的最后让他输出采样结果,再在sub2内部的开头让它输出读到的采样结果,结果发现它们不一样:
  1. ...
  2. test1  -2.10577516073414        35.3110685664510                1
  3. test2  -2.10584454662873        35.2900101209847                1
  4. test1 -5.935277974994733E-002   129.545758530043                0
  5. test2 -5.954648581507676E-002   129.544568183094                0
  6. test1 -0.104941126661693       -25.3967090962898                2
  7. test2 -0.104820942496785       -25.3998545087038                2
  8. test1  -3.55467247470428       -129.492075198021                1
  9. test2  -3.55433312249038       -129.563165448924                1
  10. test1   1.90850527754983        354.316392485028                0
  11. test2   1.90750004729617        354.373622420096                0
  12. test1 -0.761451748837811        195.512064015400                3
  13. test2 -0.761838386826844        195.489210799541                3
  14. ...
复制代码

这里test1和test2分别为sub1和sub2的输出,后两个数是采样结果,最后一个integer是线程号。可以看出它们的差距比较大。如果是DO ORDERED则两者完全一致。但我不明白为什么它们会不一样,而且因为采样的数据是需要演化的,又不能设为SHARED,这种情况应该怎么解决呢?
作者
Author:
hebrewsnabla    时间: 2020-6-13 16:48
wxhwbh 发表于 2020-6-13 16:12
我再检查了一下发现,可能是采样的问题。我在sub1内部的最后让他输出采样结果,再在sub2内部的开头让它输 ...

没有细节没法说。
作者
Author:
万里云    时间: 2020-6-14 11:03
本帖最后由 万里云 于 2020-6-14 17:35 编辑

帮楼主用MPI改写了一下:
  1. use mpi ! 声明使用mpi模块

  2. integer :: world_size, world_rank, mpi_ierr ! 总进程数、当前进程ID、子程序返回状态


  3. ! 初始化
  4. call mpi_ini(mpi_ierr)

  5. ! 获取总进程数和当前进程ID
  6. call mpi_comm_size(mpi_comm_world, world_size, mpi_ierr)
  7. call mpi_comm_rank(mpi_comm_world, world_rank, mpi_ierr)

  8. do itraj=1, Ntraj
  9.     if (mod(itraj, world_size) == world_rank) then ! 根据itraj和总进程数的余数判断当前itraj由哪个进程处理
  10.         call sub1 !抽样
  11.         call sub2(itraj) !演化
  12.     end if
  13. end do

  14. ! 结束
  15. call mpi_finalize(mpi_ierr)
复制代码




作者
Author:
wxhwbh    时间: 2020-6-14 13:41
万里云 发表于 2020-6-14 11:03
帮楼主用MPI改写了一下:

谢谢,我也在试图学习MPI,看看能不能改成MPI并行。
好像对于动力学程序这种强调顺序的,OpenMP确实不太方便,以后还是量化程序用OpenMP,动力学程序用MPI吧......
作者
Author:
wxhwbh    时间: 2020-6-14 15:41
万里云 发表于 2020-6-14 11:03
帮楼主用MPI改写了一下:

我试着按这个方法改写了程序,发现可以起到并行的作用,但最后输出的结果是正确结果的1/np倍(np是并行核数)。我的程序在演化这个部分是把最后的数据存储到一个数组P(i, j, itraj)里面,所有轨迹演化完了后对这个数组取一个轨迹系综平均:

  1. ... (前面是抽样演化部分)
  2. do i=1,M
  3.     do j=1, N
  4.         result(i,j)=sum(P(i,j,:))/Ntraj
  5.     end do
  6. end do
复制代码


我猜测是这一步出了问题。但我有一点不明白:如果在此之前已经用了mpi_finalize退出MPI环境,那么这一部分不应该是串行的吗?还会受到MPI的影响吗?另外我看有些MPI程序好像是用归约(REDUCE)的方法来求和,请问这种情况是否一定要用MPI的归约?

还有一个问题,我看到大部分的MPI教程都是说程序的开头必须用MPI_INIT,最后结束部分用MPI_FINALIZE。但我这个程序动力学只是一个subroutine,只在这个subroutine里使用MPI环境可以吗?或者说一个程序只把并行部分用MPI_INIT和MPI_FINALIZE框起来,那么可以保证只有这个部分是并行的,其他部分是串行的吗?非常感谢!
作者
Author:
万里云    时间: 2020-6-14 16:47
本帖最后由 万里云 于 2020-6-14 17:14 编辑
wxhwbh 发表于 2020-6-14 15:41
我试着按这个方法改写了程序,发现可以起到并行的作用,但最后输出的结果是正确结果的1/np倍(np是并行核 ...
openmp是“多线程”并行,进程还是只有一个,数据在线程间共享。而MPI是“多进程”并行,每个进程都是一个独立的程序,不共享数据。如果用top命令查看CPU占用,openmp是一个程序占用100%*N,而MPI是N个占用100%的程序。

如果把计算任务比作运货,CPU比做司机:openmp就是只有一辆卡车,干活时一拥而上;而MPI则是人手一辆卡车,每个人按工号分配任务,只在必要的时候交流信息。一拥而上省了买车的钱(内存),而且也不需要明确地分配任务(程序简单),适合短小的任务。但任务量一大,这种乱糟糟的管理方式就捉急了。人手一辆卡车既费钱,又需要花时间分配任务,但运作起来有条不紊,出了事容易追责,适合较大的任务。

如果需要汇总来自不同进程的数据,需要开辟一个working array并设为零,并行完成后再调用mpi_reduce或者mpi_all_reduce汇总到final array。

mpi_init必须在总程序一开始调用,同理mpi_finalize必须在总程序最后调用。一旦调用了mpi_finalize程序就退出了。

如果只在一个子程序内并行,可以用MPI_Comm_spawn动态创建进程,但估计挺复杂的。最好的办法是把主程序其它部分并行,串行部分只让主进程(world_rank==0)来干。
作者
Author:
wxhwbh    时间: 2020-6-15 10:57
万里云 发表于 2020-6-14 16:47
openmp是“多线程”并行,进程还是只有一个,数据在线程间共享。而MPI是“多进程”并行,每个进程都是一个 ...

谢谢您。程序大部分都没有问题了,但还有一个小问题:演化的第一步会生成一个随机数,但我检查发现MPI并行后并行的进程生成的随机数是一模一样的,也就是同时在跑的4个进程都生成了一样的随机数。我不是很理解,因为理论上这些变量不应该是private吗?这种情况要怎么处理呢?
作者
Author:
hebrewsnabla    时间: 2020-6-15 11:06
wxhwbh 发表于 2020-6-15 10:57
谢谢您。程序大部分都没有问题了,但还有一个小问题:演化的第一步会生成一个随机数,但我检查发现MPI并 ...

因为随机数种子是一样的。办法有很多,例如在主进程一次生成多个随机数,再scatter到每个进程;或者想办法使用不同的种子(这个不太容易)。
作者
Author:
万里云    时间: 2020-6-15 11:20
wxhwbh 发表于 2020-6-15 10:57
谢谢您。程序大部分都没有问题了,但还有一个小问题:演化的第一步会生成一个随机数,但我检查发现MPI并 ...

MPI每个进程是同时启动的,随机函数生成器读取系统时间做种子值,种子值也是一样的。

试试用这个子程序初始化种子值:
  1.   subroutine init_random_seed()
  2.     implicit none
  3.     integer :: i, n, clock
  4.     integer, dimension(:), allocatable :: seed

  5.     call random_seed(size = n)
  6.     allocate(seed(n))

  7.     call system_clock(count=clock)

  8.     seed = clock / world_size * (world_rank + 1) &
  9.          + 37 * (/ (i - 1, i = 1, n) /)
  10.     call random_seed(put = seed)

  11.     deallocate(seed)
  12.   end subroutine
复制代码

作者
Author:
wxhwbh    时间: 2020-6-15 13:44
本帖最后由 wxhwbh 于 2020-6-15 13:53 编辑
万里云 发表于 2020-6-15 11:20
MPI每个进程是同时启动的,随机函数生成器读取系统时间做种子值,种子值也是一样的。

试试用这个子程 ...

谢谢,用了这个后确实能生成不同的随机数。
这个程序是什么原理呢?我好像在别的程序也见过类似的代码。另外这里的n是指进程数吗?

作者
Author:
万里云    时间: 2020-6-15 16:08
wxhwbh 发表于 2020-6-15 13:44
谢谢,用了这个后确实能生成不同的随机数。
这个程序是什么原理呢?我好像在别的程序也见过类似的代码。 ...

原理是把种子值根据进程ID做了运算,让不同进程的种子差别尽可能大。

这段程序的主体也是我抄来的,n是啥我也不清楚,那个莫名其妙的37是什么意思也不清楚。






欢迎光临 计算化学公社 (http://bbs.keinsci.com/) Powered by Discuz! X3.3