JuliaでMPIによる並列計算

# お詫び
Gatherv!を使ったコードにバグがあったので，修正版のコードを載せました。

# MPIによる並列計算
Juliaのコードで並列計算したいとおもって，MPIによる並列計算をテスト。
（Juliaのプロセス並列も試してみたが，オーバーヘッドが大きいのか並列効果が出なくてMPIをためそうとしてする状態です）

https://qiita.com/cometscome_phys/items/f1971f8590a6d69d472b

上記の記事を参考にさせてもらいました。

# MPIの準備
MPIを使えるようにしておきます。その状態でJuliaのREPLで

add MPI

しておきます。

# 行列積の計算
## コード
ベタですが，行列積の計算。自分がマスターのころ（15年ほど前）TAやってたMPIの講義の資料を引っ張り出して，後輩たちのコードを見つつ作成。

```julia
using MPI

function main()
    MPI.Init() # MPI初期化
    comm = MPI.COMM_WORLD
    nprocs = MPI.Comm_size(comm)
    myrank = MPI.Comm_rank(comm)
    println("myrank/nprocs=", myrank, "/", nprocs)

    l=3000
    m=3000
    n=3000
    A = zeros(l, n)
    B = ones(l, m)
    C = 0.5 .* ones(m, n)
    Atemp = zeros(l, n)
    
    ista, iend = parallel_range(1, m, nprocs, myrank)
    println("myrank=", myrank, ", ista, iend=", ista, ", ", iend)
    
    for k=ista:iend
        for j=1:n
            for i=1:l
                Atemp[i,j] = Atemp[i,j]+B[i,k]C[k,j]
            end
        end
    end
    
    A = MPI.Reduce(Atemp, MPI.SUM, 0, comm)
    
    MPI.Barrier(comm)
    
    if myrank == 0 
        println("A[1:5,1:5]=", A[1:5, 1:5])
        println("A[3000-5:3000,3000-5:3000]=", A[3000-5:3000,3000-5:3000])
    end

    MPI.Finalize() #MPI終了
end

function parallel_range(n1, n2, nprocs, irank)
    nb = div(n2-n1+1, nprocs)
    nm = (n2-n1+1)%nprocs
    ista = irank*nb+n1 + min(irank, nm)
    iend = ista+nb-1 
    if nm > irank
        iend = iend+1
    end
    return ista, iend
end

@time main()

```

各プロセスで計算した計算結果をAtempに保存して，MPI.ReduceでAtempの結果をAに集約（合算）します。


処理する要素を分割するparallel_rangeですが，当時のテキストをコピーしました。
可能な限り，同程度の要素数になるように分割します。要素数がプロセス数で割り切れなくても
うまく分割できます。


## 実行
ソースコードをMPI-matrixmul01.jlとして保存したとして，以下で実行。環境はLinuxです。
Macだとmpirunだと動かないかも。出てくるコメントに応じて適宜変更すると動きます。

8並列くらいまでは並列性能が出るのを確かめました。

```
mpirun -np 9 julia MPI-matrixmul01.jl 
```

## MPI.Gatherを使った場合
データの集約にMPI.Gatherを使った場合もやってみました。MPI.Gatherの返り値に計算結果が入りますが，なぜか1次元配列になります。なので，あとからreshapeしてます。
理由ご存知だったら教えていただけるとうれしいです。

MPI.Gatherだと送信バッファのサイズがすべて等しいと仮定するので，ランクごとに送信バッファのサイズが変わるとエラーします。


```julia
using MPI

function main()
    MPI.Init() # MPI初期化
    comm = MPI.COMM_WORLD
    nprocs = MPI.Comm_size(comm)
    myrank = MPI.Comm_rank(comm)
    println("myrank/nprocs=", myrank, "/", nprocs)

    l=3000
    m=2079
    n=3000
    A = zeros(l, n)
    B = ones(l, m)
    C = 0.5 .* ones(m, n)
       
    jsta, jend = parallel_range(1, n, nprocs, myrank)
    println("myrank=", myrank, ", jsta, jend=", jsta, ", ", jend)
    
    for j=jsta:jend
        for k=1:m
            for i=1:l
                A[i,j] = A[i,j]+B[i,k]C[k,j]
            end
        end
    end
    
     A = MPI.Gather(A[:,jsta:jend], 0, comm)
    
    MPI.Barrier(comm)
    
    if myrank == 0 
        A=reshape(A, 3000, 3000)
        println("A[1:5,1:5]=", A[1:5, 1:5])
        println("A[3000-5:3000,3000-5:3000]=", A[3000-5:3000,3000-5:3000])
    end

    MPI.Finalize() #MPI終了
end

function parallel_range(n1, n2, nprocs, irank)
    nb = div(n2-n1+1, nprocs)
    nm = (n2-n1+1)%nprocs
    ista = irank*nb+n1 + min(irank, nm)
    iend = ista+nb-1 
    if nm > irank
        iend = iend+1
    end
    return ista, iend
end

@time main()
```

## MPI.Send, MPI.Recvを使った場合
send, recvを使った場合。受け取るrecv側の配列にはA[:,jsta:jend]のようなスライスが使えなかったので，別途配列を作って受け取ってます（tempの部分）。tempのデータを元の配列に戻す処理が必要になってくるので若干手間が増えます。


```julia
using MPI

function main()
    MPI.Init() # MPI初期化
    comm = MPI.COMM_WORLD
    nprocs = MPI.Comm_size(comm)
    myrank = MPI.Comm_rank(comm)
    println("myrank/nprocs=", myrank, "/", nprocs)

    l=3000
    m=2080
    n=3000
    #l=5
    #m=4
    #n=5
    A = zeros(l, n)
    B = ones(l, m)
    C = 0.5 .* ones(m, n)
    
    temp = zeros(l,n)
       
    jsta, jend = parallel_range(1, n, nprocs, myrank)
    println("myrank=", myrank, ", jsta, jend=", jsta, ", ", jend)
    
    for j=jsta:jend
        for k=1:m
            for i=1:l
                A[i,j] = A[i,j]+B[i,k]C[k,j]
            end
        end
    end
    
    if myrank == 0
        for rank=1:nprocs-1
            MPI.Recv!(temp, rank, 0, comm)
            println("temp",temp[1:2,1])
            jsta, jend = parallel_range(1, n, nprocs, rank)
            println("jsta,jend=", jsta, ", ", jend)
            A[:,jsta:jend] .= temp[:,jsta-jsta+1:jend-jsta+1]
        end
    else 
        MPI.Send(A[:,jsta:jend], 0, 0, comm)
    end
    
    if myrank == 0 
        println("A[1:5,1:5]=", A[1:5, 1:5])
        println("A[3000-5:3000,3000-5:3000]=", A[l-5:l,l-5:l])
    end

    MPI.Finalize() #MPI終了
end

function parallel_range(n1, n2, nprocs, irank)
    nb = div(n2-n1+1, nprocs)
    nm = (n2-n1+1)%nprocs
    ista = irank*nb+n1 + min(irank, nm)
    iend = ista+nb-1 
    if nm > irank
        iend = iend+1
    end
    return ista, iend
end

@time main()
```

## MPI.Gatherv!を使った場合
Gatherv!を使えば，各ランクで要素数が違っても対応できます。そのために，各ランクで送受信の対象となるデータの要素数を先に数えておく必要があります。それがsizesという配列の計算の部分です。

Gatherv!では受信バッファにはVBufferを作ってやります。VBuffer(A, sizes)でAにデータが集約されます。

配列データを送受信するとき，受信する側では配列の列に沿った方向に順番にデータが格納されます。そのため，配列は列に沿った方向に分割する必要があります。


### 修正版のコード

```julia
using MPI

function main()
    MPI.Init() # MPI初期化
    comm = MPI.COMM_WORLD
    nprocs = MPI.Comm_size(comm)
    myrank = MPI.Comm_rank(comm)
    println("myrank/nprocs=", myrank, "/", nprocs)

    l=3000
    m=2079
    n=3000
    A = zeros(l, n)
    B = ones(l, m)
    C = 0.5 .* ones(m, n)
    Atemp = similar(A)
    
    sizes = zeros(Int64, nprocs)
    
    if myrank == 0
        for i=0:nprocs-1
            jsta, jend = parallel_range(1, n, nprocs, i)
            sizes[i+1] = l*(jend-jsta+1)
        end
        println("sizes=", sizes, ", ", sum(sizes))
    end
    
    jsta, jend = parallel_range(1, n, nprocs, myrank)
    println("myrank=", myrank, ", jsta, jend=", jsta, ", ", jend)

    for j=jsta:jend
        for k=1:m
            for i=1:l
                Atemp[i,j] = Atemp[i,j]+B[i,k]C[k,j]
            end
        end
    end
    
    if myrank == 0
        MPI.Gatherv!(Atemp[:,jsta:jend], VBuffer(A, sizes), 0, comm)
    else
        MPI.Gatherv!(Atemp[:,jsta:jend], VBuffer(nothing), 0, comm)
    end

    #MPI.Barrier(comm)
    
    if myrank == 0 
        println(findall(x->(x!=1039.5), A)) #値のチェック
        println("A[1:5,1:5]=", A[1:5, 1:5])
        println("A[3000-5:3000,3000-5:3000]=", A[3000-5:3000,3000-5:3000])
    end

    MPI.Finalize() #MPI終了
end

function parallel_range(n1, n2, nprocs, irank)
    nb = div(n2-n1+1, nprocs)
    nm = (n2-n1+1)%nprocs
    ista = irank*nb+n1 + min(irank, nm)
    iend = ista+nb-1 
    if nm > irank
        iend = iend+1
    end
    return ista, iend
end

@time main()

```

### お詫び
下記のコードだと，jsta, jendの範囲が正しく計算されず，正しい結果が得られません。お詫びします。
上記に修正したコードを載せます。

修正前コード

```julia
using MPI

function main()
    MPI.Init() # MPI初期化
    comm = MPI.COMM_WORLD
    nprocs = MPI.Comm_size(comm)
    myrank = MPI.Comm_rank(comm)
    println("myrank/nprocs=", myrank, "/", nprocs)

    l=3000
    m=2079
    n=3000
    A = zeros(l, n)
    B = ones(l, m)
    C = 0.5 .* ones(m, n)
    Atemp = similar(A)
    
    sizes = zeros(Int64, nprocs)

    jsta, jend = parallel_range(1, n, nprocs, myrank)
    println("myrank=", myrank, ", jsta, jend=", jsta, ", ", jend)
    
    if myrank == 0
        for i=0:nprocs-1
            jsta, jend = parallel_range(1, n, nprocs, i)
            sizes[i+1] = l*(jend-jsta+1)
        end
        println("sizes=", sizes, ", ", sum(sizes))
    end

    for j=jsta:jend
        for k=1:m
            for i=1:l
                Atemp[i,j] = Atemp[i,j]+B[i,k]C[k,j]
            end
        end
    end
    
    if myrank == 0
        MPI.Gatherv!(Atemp[:,jsta:jend], VBuffer(A, sizes), 0, comm)
    else
        MPI.Gatherv!(Atemp[:,jsta:jend], VBuffer(nothing), 0, comm)
    end

    #MPI.Barrier(comm)
    
    if myrank == 0 
        A=reshape(A, 3000, 3000)
        println("A[1:5,1:5]=", A[1:5, 1:5])
        println("A[3000-5:3000,3000-5:3000]=", A[3000-5:3000,3000-5:3000])
    end

    MPI.Finalize() #MPI終了
end

function parallel_range(n1, n2, nprocs, irank)
    nb = div(n2-n1+1, nprocs)
    nm = (n2-n1+1)%nprocs
    ista = irank*nb+n1 + min(irank, nm)
    iend = ista+nb-1 
    if nm > irank
        iend = iend+1
    end
    return ista, iend
end

@time main()
```

## 計算速度
MPI.Gatherv!使った場合の結果の抜粋。

```bash
1コア
myrank/nprocs=0/1
myrank=0, jsta, jend=1, 3000
sizes=[9000000], 9000000
A[1:5,1:5]=[1039.5 1039.5 1039.5 1039.5 1039.5; 1039.5 1039.5 1039.5 1039.5 1039.5; 1039.5 1039.5 1039.5 1039.5 1039.5; 1039.5 1039.5 1039.5 1039.5 1039.5; 1039.5 1039.5 1039.5 1039.5 1039.5]
A[3000-5:3000,3000-5:3000]=[1039.5 1039.5 1039.5 1039.5 1039.5 1039.5; 1039.5 1039.5 1039.5 1039.5 1039.5 1039.5; 1039.5 1039.5 1039.5 1039.5 1039.5 1039.5; 1039.5 1039.5 1039.5 1039.5 1039.5 1039.5; 1039.5 1039.5 1039.5 1039.5 1039.5 1039.5; 1039.5 1039.5 1039.5 1039.5 1039.5 1039.5]
 49.078051 seconds (936.48 k allocations: 402.841 MiB, 0.49% gc time, 1.13% compilation time)

5コア
[1800000, 1800000, 1800000, 1800000, 1800000], 9000000
A[1:5,1:5]=[1039.5 1039.5 1039.5 1039.5 1039.5; 1039.5 1039.5 1039.5 1039.5 1039.5; 1039.5 1039.5 1039.5 1039.5 1039.5; 1039.5 1039.5 1039.5 1039.5 1039.5; 1039.5 1039.5 1039.5 1039.5 1039.5]
A[3000-5:3000,3000-5:3000]=[1039.5 1039.5 1039.5 1039.5 1039.5 1039.5; 1039.5 1039.5 1039.5 1039.5 1039.5 1039.5; 1039.5 1039.5 1039.5 1039.5 1039.5 1039.5; 1039.5 1039.5 1039.5 1039.5 1039.5 1039.5; 1039.5 1039.5 1039.5 1039.5 1039.5 1039.5; 1039.5 1039.5 1039.5 1039.5 1039.5 1039.5]
  8.579884 seconds (936.51 k allocations: 347.910 MiB, 1.61% gc time, 5.38% compilation time)

9コア
sizes=[1002000, 1002000, 1002000, 999000, 999000, 999000, 999000, 999000, 999000], 9000000
A[1:5,1:5]=[1039.5 1039.5 1039.5 1039.5 1039.5; 1039.5 1039.5 1039.5 1039.5 1039.5; 1039.5 1039.5 1039.5 1039.5 1039.5; 1039.5 1039.5 1039.5 1039.5 1039.5; 1039.5 1039.5 1039.5 1039.5 1039.5]
A[3000-5:3000,3000-5:3000]=[1039.5 1039.5 1039.5 1039.5 1039.5 1039.5; 1039.5 1039.5 1039.5 1039.5 1039.5 1039.5; 1039.5 1039.5 1039.5 1039.5 1039.5 1039.5; 1039.5 1039.5 1039.5 1039.5 1039.5 1039.5; 1039.5 1039.5 1039.5 1039.5 1039.5 1039.5; 1039.5 1039.5 1039.5 1039.5 1039.5 1039.5]
  5.390722 seconds (936.55 k allocations: 341.800 MiB, 3.50% gc time, 8.65% compilation time)  5.410653 seconds (79.32 k allocations: 292.609 MiB, 3.54% gc time, 1.78% compilation time)

10コア
sizes=[900000, 900000, 900000, 900000, 900000, 900000, 900000, 900000, 900000, 900000], 9000000
A[1:5,1:5]=[1039.5 1039.5 1039.5 1039.5 1039.5; 1039.5 1039.5 1039.5 1039.5 1039.5; 1039.5 1039.5 1039.5 1039.5 1039.5; 1039.5 1039.5 1039.5 1039.5 1039.5; 1039.5 1039.5 1039.5 1039.5 1039.5]
A[3000-5:3000,3000-5:3000]=[1039.5 1039.5 1039.5 1039.5 1039.5 1039.5; 1039.5 1039.5 1039.5 1039.5 1039.5 1039.5; 1039.5 1039.5 1039.5 1039.5 1039.5 1039.5; 1039.5 1039.5 1039.5 1039.5 1039.5 1039.5; 1039.5 1039.5 1039.5 1039.5 1039.5 1039.5; 1039.5 1039.5 1039.5 1039.5 1039.5 1039.5]
  6.697126 seconds (79.31 k allocations: 291.854 MiB, 2.35% gc time, 1.66% compilation time)
  6.704198 seconds (936.56 k allocations: 341.045 MiB, 2.35% gc time, 8.01% compilation time)

20コア
sizes=[450000, 450000, 450000, 450000, 450000, 450000, 450000, 450000, 450000, 450000, 450000, 450000, 450000, 450000, 450000, 450000, 450000, 450000, 450000, 450000], 9000000
A[1:5,1:5]=[1039.5 1039.5 1039.5 1039.5 1039.5; 1039.5 1039.5 1039.5 1039.5 1039.5; 1039.5 1039.5 1039.5 1039.5 1039.5; 1039.5 1039.5 1039.5 1039.5 1039.5; 1039.5 1039.5 1039.5 1039.5 1039.5]
A[3000-5:3000,3000-5:3000]=[1039.5 1039.5 1039.5 1039.5 1039.5 1039.5; 1039.5 1039.5 1039.5 1039.5 1039.5 1039.5; 1039.5 1039.5 1039.5 1039.5 1039.5 1039.5; 1039.5 1039.5 1039.5 1039.5 1039.5 1039.5; 1039.5 1039.5 1039.5 1039.5 1039.5 1039.5; 1039.5 1039.5 1039.5 1039.5 1039.5 1039.5]
  5.459388 seconds (79.31 k allocations: 288.420 MiB, 3.14% gc time, 2.05% compilation time)  5.461391 seconds (79.31 k allocations: 288.420 MiB, 3.16% gc time, 2.05% compilation time)

40コア
sizes=[225000, 225000, 225000, 225000, 225000, 225000, 225000, 225000, 225000, 225000, 225000, 225000, 225000, 225000, 225000, 225000, 225000, 225000, 225000, 225000, 225000, 225000, 225000, 225000, 225000, 225000, 225000, 225000, 225000, 225000, 225000, 225000, 225000, 225000, 225000, 225000, 225000, 225000, 225000, 225000], 9000000
A[1:5,1:5]=[1039.5 1039.5 1039.5 1039.5 1039.5; 1039.5 1039.5 1039.5 1039.5 1039.5; 1039.5 1039.5 1039.5 1039.5 1039.5; 1039.5 1039.5 1039.5 1039.5 1039.5; 1039.5 1039.5 1039.5 1039.5 1039.5]
A[3000-5:3000,3000-5:3000]=[1039.5 1039.5 1039.5 1039.5 1039.5 1039.5; 1039.5 1039.5 1039.5 1039.5 1039.5 1039.5; 1039.5 1039.5 1039.5 1039.5 1039.5 1039.5; 1039.5 1039.5 1039.5 1039.5 1039.5 1039.5; 1039.5 1039.5 1039.5 1039.5 1039.5 1039.5; 1039.5 1039.5 1039.5 1039.5 1039.5 1039.5]
  6.470760 seconds (79.31 k allocations: 286.704 MiB, 3.23% gc time, 1.76% compilation time)
```