【问题标题】:OpenMPI IPC performance is worse than reading/writing to fileOpenMPI IPC 性能比读/写文件差
【发布时间】:2016-11-04 06:08:45
【问题描述】:

我正在尝试 IPC 的各种方法来执行以下操作:

  1. Master 启动。
  2. Master 启动一个 Slave。
  3. Master 将一个数组传递给 Slave。
  4. Slave 处理数组。
  5. 从设备将阵列发回给主设备。

我已经尝试使用 OpenMPI 来解决这个问题,方法是让父进程生成一个子进程,然后由子进程执行上述处理。但是,我也尝试过 - 我认为这是最糟糕的方法 - 让 master 将数据写入文件并让 slave 读取和写入该文件。 结果令人惊叹。

以下是我实现这一目标的两种方式。第一种方式是“文件”方式,第二种方式是使用OpenMPI。

Master.f90

program master
implicit none

integer*4, dimension (10000) :: matrix
integer :: length, i, exitstatus, cmdstatus
logical :: waistatus

! put integers in matrix and output data into a file 
open(1, file='matrixdata.dat', status='new')

length = 10000

do i=1,length
    matrix(i) = i
    write(1,*) matrix(i)
end do

close(1)

call execute_command_line("./slave.out", wait = .true., exitstat=exitstatus)

if(exitstatus .eq. 0) then
    ! open and read the file changed by subroutine slave
    open(1, file= 'matrixdata.dat', status='old')
    do i = 1, length
        read(1,*) matrix(i)
    end do
    close(1)
endif

end program master

Slave.f90

program slave
implicit none

    integer*4, dimension (10000) :: matrix
    integer :: length, i

    ! Open and read the file made by master into a matrix
    open (1, file= 'matrixdata.dat', status = 'old')
    length = 10000

    do i = 1, length
        read(1,*) matrix(i)
    end do
    close(1)

    ! Square all numbers and write over the file with new data
    open(1, file= 'matrixdata.dat', status = 'old')
    do i=1,length
        matrix(i) = matrix(i)**2
        write(1,*) matrix(i)
    end do
    close(1)

end program slave

* OpenMPI *

Master.f90

program master
use mpi
implicit none

    integer :: ierr, num_procs, my_id, intercomm, i, siz, array(10000000), s_tag, s_dest, siffra

    CALL MPI_INIT(ierr)

    CALL MPI_COMM_RANK(MPI_COMM_WORLD, my_id, ierr)
    CALL MPI_COMM_SIZE(MPI_COMM_WORLD, num_procs, ierr)

    siz = 10000

    !print *, "S.Rank =", my_id
    !print *, "S.Size =", num_procs

    if (.not. (ierr .eq. 0)) then
        print*, "S.Unable to initilaize bös!"
        stop
    endif

    do i=1,size(array)
        array(i) = 2
    enddo

    if (my_id .eq. 0) then
        call MPI_Comm_spawn("./slave.out", MPI_ARGV_NULL, 1, MPI_INFO_NULL, my_id, &
        & MPI_COMM_WORLD, intercomm, MPI_ERRCODES_IGNORE, ierr)


        s_dest = 0 !rank of destination (integer)
        s_tag =  1 !message tag (integer)
        call MPI_Send(array(1), siz, MPI_INTEGER, s_dest, s_tag, intercomm, ierr)

        call MPI_Recv(array(1), siz, MPI_INTEGER, s_dest, s_tag, intercomm, MPI_STATUS_IGNORE, ierr)

        !do i=1,10
        !   print *, "S.Array(",i,"): ", array(i)
        !enddo

    endif

    call MPI_Finalize(ierr)

end program master

Slave.f90

program name
use mpi
implicit none

    ! type declaration statements
    integer :: ierr, parent, my_id, n_procs, i, siz, array(10000000), ctag, csource, intercomm, siffra
    logical :: flag

    siz = 10000

    ! executable statements
    call MPI_Init(ierr)
    call MPI_Initialized(flag, ierr)
    call MPI_Comm_get_parent(parent, ierr)
    call MPI_Comm_rank(MPI_COMM_WORLD, my_id, ierr)
    call MPI_Comm_size(MPI_COMM_WORLD, n_procs, ierr)

    csource = 0 !rank of source
    ctag = 1 !message tag

    call MPI_Recv(array(1), siz, MPI_INTEGER, csource, ctag, parent, MPI_STATUS_IGNORE, ierr)

    !do i=1,10
    !    print *, "C.Array(",i,"): ", array(i)
    !enddo

    do i=1,size(array)
        array(i) = array(i)**2
    enddo

    !do i=1,10
    !    print *, "C.Array(",i,"): ", array(i)
    !enddo

    call MPI_Send(array(1), siz, MPI_INTEGER, csource, ctag, parent, ierr)

    call MPI_Finalize(ierr)
end program name

现在,有趣的是,通过使用time 程序,我测得执行“程序的文件版本”需要 19.8 毫秒。 OpenMPI 版本需要 60 毫秒。为什么?如果您使用

我尝试将数组增加到 10^5 个整数。文件版本在 114 毫秒内执行,OpenMPI 在 53 毫秒内执行。当增加到 10^6 整数文件时:1103 ms,OpenMPI:77ms。

开销真的那么大吗?

【问题讨论】:

  • 您的 MPI 时间为 60 毫秒。然后,您增加数组大小和 MPI 时间减少到 53 毫秒!?这些数组大小太小,无法使用time 命令准确测量。当我这样做时,我将计时/基准嵌入代码本身并在前后使用clock_gettime(CLOCK_REALTIME,...),重复运行大约 10 次,并使用最短的时间来解决启动问题、时间片问题。
  • 在您的open 语句中使用newunit 说明符以避免严重的冲突。在大型程序中手动设置连接的文件单元极易出错且难以维护。

标签: performance fortran ipc openmpi


【解决方案1】:

从根本上说,对适合缓存的问题大小使用分布式处理是没有意义的(除了在一些微不足道的并行情况下)。典型的使用场景是用于比 LLC 大得多的数据传输。即使你最大的情况(10^6)也适合现代缓存。

首先,对于写入磁盘的方法,您必须了解页面缓存在您的操作系统中的影响。如果您的 MPI 进程在同一芯片上,操作系统只会听到“执行写入”然后“执行读取”。如果在此期间没有任何东西污染页面缓存,那么它只会从 RAM 中获取数据,而不是与磁盘相反。更好的实验是在写入和读取之间刷新页面缓存(至少在 linux 上,这是可能的,通过 shell 命令)。实际上,如果您从页面缓存中获取数据,您就是在执行共享内存处理。

此外,您在命令行上使用了time,因此您将 MPI 初始化和建立通信接口所需的时间与几个函数调用相结合。这不是一个好的基准,因为为磁盘 IO 方法提供的接口已经被操作系统初始化。同样对于如此小的问题规模,与程序主体的运行时间相比,MPI 的初始化是不平凡的。正确的做法是在代码中进行计时。

对于这两种方法,您应该预期线性缩放会因方法的开销而产生偏差。事实上,当数据大小超过 LLC 和页面缓存时,您应该会看到一些机制。最好的方法是在 n=12,13,..24 时使用 ARRAY_SIZE=2^n 重复运行并查看曲线。

【讨论】:

  • 感谢您的回复!但是,如果我可能会问,you 如何解决这样的问题?如果我们正在讨论在数据介于 1kiB 到 100 kiB 之间的进程之间传输数据,你会怎么做?
  • 如果你在做分布式处理,你会使用 MPI,但如果你使用分布式处理来提高性能,而不是增加能力(即增加可用内存),你真的必须评估如果它是实现您的代码或算法的合适模型。
猜你喜欢
  • 2017-02-24
  • 1970-01-01
  • 2016-09-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-10-03
相关资源
最近更新 更多