Performa jelek dengan MPI

Jan 03 2021

Saya sedang mempelajari MPI dan memiliki pertanyaan tentang hampir tidak ada peningkatan kinerja dalam penerapan sederhana di bawah ini.

#include <stdio.h>
#include <stdlib.h>
#include <mpi.h>

int main(int argc, char **argv)
{
        int mpirank, mpisize;
        int tabsize = atoi(*(argv + 1));

        MPI_Init(&argc, &argv);
        MPI_Comm_rank(MPI_COMM_WORLD, &mpirank);
        MPI_Comm_size(MPI_COMM_WORLD, &mpisize);

        unsigned long int sum = 0;
        int rcvsize = tabsize / mpisize;
        int *rcvbuf = malloc(rcvsize * sizeof(int));
        int *tab = malloc(tabsize * sizeof(int));
        int totalsum = 0;

        if(mpirank == 0){
            for(int i=0; i < tabsize; i++){
               *(tab + i) = 1;
            }
        }
        MPI_Scatter(tab, tabsize/mpisize, MPI_INT, rcvbuf, tabsize/mpisize, MPI_INT, 0, MPI_COMM_WORLD);

        for(int i=0; i < tabsize/mpisize; i++){
                sum += *(rcvbuf + i);
        }

        MPI_Reduce(&sum, &totalsum, 1, MPI_INT, MPI_SUM, 0, MPI_COMM_WORLD);

        if(mpirank == 0){
                printf("The totalsum = %li\n", totalsum);
        }

        MPI_Finalize();

        return 0;
}

Waktu pelaksanaan implementasi di atas adalah:

$ /usr/bin/time mpirun -np 1 test1 2000000000 The totalsum = 2000000000 13.76user 3.31system 0:17.30elapsed 98%CPU (0avgtext+0avgdata 15629824maxresident)k 0inputs+8outputs (0major+21720minor)pagefaults 0swaps $ /usr/bin/time mpirun -np 1 test1 2000000000
The totalsum = 2000000000
13.78user 3.29system 0:17.31elapsed 98%CPU (0avgtext+0avgdata 15629824maxresident)k 0inputs+8outputs (0major+21717minor)pagefaults 0swaps
$ /usr/bin/time mpirun -np 1 test1 2000000000 The totalsum = 2000000000 13.78user 3.32system 0:17.33elapsed 98%CPU (0avgtext+0avgdata 15629828maxresident)k 0inputs+8outputs (0major+20697minor)pagefaults 0swaps $ /usr/bin/time mpirun -np 20 test1 2000000000
The totalsum = 2000000000
218.42user 6.10system 0:12.99elapsed 1727%CPU (0avgtext+0avgdata 8209484maxresident)k 0inputs+17400outputs (118major+82587minor)pagefaults 0swaps
$ /usr/bin/time mpirun -np 20 test1 2000000000 The totalsum = 2000000000 216.17user 6.37system 0:12.89elapsed 1726%CPU (0avgtext+0avgdata 8209488maxresident)k 0inputs+17168outputs (126major+81092minor)pagefaults 0swaps $ /usr/bin/time mpirun -np 20 test1 2000000000
The totalsum = 2000000000
216.16user 6.09system 0:12.88elapsed 1724%CPU (0avgtext+0avgdata 8209492maxresident)k 0inputs+17192outputs (111major+81665minor)pagefaults 0swaps

Yang hanya memberikan keuntungan kinerja sekitar 25%. Dugaan saya di sini adalah bahwa kemacetan mungkin disebabkan oleh proses yang bersaing untuk mengakses memori. Kemudian saya mencoba yang sama tetapi tanpa menggunakan memori untuk mendapatkan data.

#include <stdio.h>
#include <stdlib.h>
#include <mpi.h>

int main(int argc, char **argv)
{
        int mpirank, mpisize;
        int tabsize = atoi(*(argv + 1));

        MPI_Init(&argc, &argv);
        MPI_Comm_rank(MPI_COMM_WORLD, &mpirank);
        MPI_Comm_size(MPI_COMM_WORLD, &mpisize);

        unsigned long int sum = 0;

        for(int i=0; i < tabsize/mpisize; i++){
                sum += 1;
        }

        MPI_Reduce(&sum, &totalsum, 1, MPI_INT, MPI_SUM, 0, MPI_COMM_WORLD);

        if(mpirank == 0){
                printf("The totalsum = %li\n", totalsum);
        }

        MPI_Finalize();

        return 0;
}

yang memberikan hasil sebagai berikut:

$ /usr/bin/time mpirun -np 1 test2 2000000000 The totalsum = 2000000000 6.17user 0.11system 0:06.49elapsed 96%CPU (0avgtext+0avgdata 5660maxresident)k 0inputs+8outputs (0major+4005minor)pagefaults 0swaps $ /usr/bin/time mpirun -np 1 test2 2000000000 
The totalsum = 2000000000
6.16user 0.12system 0:06.49elapsed 96%CPU (0avgtext+0avgdata 5660maxresident)k 0inputs+8outputs (0major+4007minor)pagefaults 0swaps 
$ /usr/bin/time mpirun -np 1 test2 2000000000 The totalsum = 2000000000 6.15user 0.11system 0:06.47elapsed 96%CPU (0avgtext+0avgdata 5664maxresident)k 0inputs+8outputs (0major+4005minor)pagefaults 0swaps $ /usr/bin/time mpirun -np 20 test2 2000000000
The totalsum = 2000000000
8.67user 2.41system 0:01.06elapsed 1040%CPU (0avgtext+0avgdata 6020maxresident)k 0inputs+16824outputs (128major+49952minor)pagefaults 0swaps
$ /usr/bin/time mpirun -np 20 test2 2000000000 The totalsum = 2000000000 8.59user 2.74system 0:01.05elapsed 1076%CPU (0avgtext+0avgdata 6028maxresident)k 0inputs+16792outputs (131major+49960minor)pagefaults 0swaps $ /usr/bin/time mpirun -np 20 test2 2000000000
The totalsum = 2000000000
8.65user 2.61system 0:01.06elapsed 1058%CPU (0avgtext+0avgdata 6024maxresident)k 0inputs+16792outputs (116major+50002minor)pagefaults 0swaps

Ini menunjukkan sekitar 83% perolehan kinerja dan akan mengkonfirmasi tebakan saya. Lalu dapatkah Anda memberi tahu saya jika tebakan saya benar dan jika ya, apakah ada cara untuk meningkatkan implementasi pertama dengan akses memori?

Kode telah dijalankan pada mesin dengan 20 inti fisik.

EDIT1: hasil tambahan dari implementasi pertama untuk proses 2, 5 dan 10:

$ /usr/bin/time mpirun -np 2 test1 2000000000 The totalsum = 2000000000 24.05user 3.40system 0:14.03elapsed 195%CPU (0avgtext+0avgdata 11724552maxresident)k 0inputs+960outputs (6major+23195minor)pagefaults 0swaps $ /usr/bin/time mpirun -np 5 test1 2000000000
The totalsum = 2000000000
55.27user 3.54system 0:12.88elapsed 456%CPU (0avgtext+0avgdata 9381132maxresident)k 0inputs+4512outputs (26major+31614minor)pagefaults 0swaps

$ /usr/bin/time mpirun -np 10 test1 2000000000
The totalsum = 2000000000
106.43user 4.07system 0:12.44elapsed 887%CPU (0avgtext+0avgdata 8599952maxresident)k 0inputs+8720outputs (51major+50059minor)pagefaults 0swaps

EDIT2:

Saya telah meletakkan MPI_Wtime () untuk mengukur MPI_Scatter bagian dari implementasi pertama sebagai berikut:

...
                for(int i=0; i < tabsize; i++){
                        *(tab + i) = 1;
                }
        }

        MPI_Barrier(MPI_COMM_WORLD);
        double start = MPI_Wtime();

        MPI_Scatter(tab, tabsize/mpisize, MPI_INT, rcvbuf, tabsize/mpisize, MPI_INT, 0, MPI_COMM_WORLD);

        MPI_Barrier(MPI_COMM_WORLD);
        double end = MPI_Wtime();

        for(int i=0; i < tabsize/mpisize; i++){
                sum += *(rcvbuf + i);
...

dan mendapatkan hasil sebagai berikut:

$ /usr/bin/time mpirun -np 1 test1 400000000
The MPI_Scatter time = 0.576 (14% of total)
3.13user 0.74system 0:04.08elapsed 95%CPU 
$ /usr/bin/time mpirun -np 2 test1 400000000 The MPI_Scatter time = 0.580 (18% of total) 5.19user 0.79system 0:03.25elapsed 183%CPU $ /usr/bin/time mpirun -np 4 test1 400000000
The MPI_Scatter time = 0.693 (22.5% of total)
9.99user 1.05system 0:03.07elapsed 360%CPU
$ /usr/bin/time mpirun -np 5 test1 400000000 The MPI_Scatter time = 0.669 (22.3% of total) 12.41user 1.01system 0:03.00elapsed 446%CPU $ /usr/bin/time mpirun -np 8 test1 400000000
The MPI_Scatter time = 0.696 (23.7% of total)
19.67user 1.25system 0:02.95elapsed 709%CPU 
$ /usr/bin/time mpirun -np 10 test1 400000000 The MPI_Scatter time = 0.701 (24% of total) 24.21user 1.45system 0:02.92elapsed 876%CPU $ /usr/bin/time mpirun -np 1 test1 1000000000
The MPI_Scatter time = 1.434 (15% of total)
7.64user 1.71system 0:09.57elapsed 97%CPU
$ /usr/bin/time mpirun -np 2 test1 1000000000 The MPI_Scatter time = 1.441 (19% of total) 12.72user 1.75system 0:07.52elapsed 192%CPU $ /usr/bin/time mpirun -np 4 test1 1000000000
The MPI_Scatter time = 1.710 (25% of total)
24.16user 1.93system 0:06.84elapsed 381%CPU
$ /usr/bin/time mpirun -np 5 test1 1000000000 The MPI_Scatter time = 1.675 (25% of total) 30.29user 2.10system 0:06.81elapsed 475%CPU $ /usr/bin/time mpirun -np 10 test1 1000000000
The MPI_Scatter time = 1.753 (26.6% of total)
59.89user 2.47system 0:06.60elapsed 943%CPU

$ /usr/bin/time mpirun -np 10 test1 100000000 The MPI_Scatter time = 0.182 (15.8% of total) 6.75user 1.07system 0:01.15elapsed 679%CPU $ /usr/bin/time mpirun -np 10 test1 200000000
The MPI_Scatter time = 0.354 (20% of total)
12.50user 1.12system 0:01.71elapsed 796%CPU 
$ /usr/bin/time mpirun -np 10 test1 300000000 The MPI_Scatter time = 0.533 (22.8% of total) 18.54user 1.30system 0:02.33elapsed 849%CPU $ /usr/bin/time mpirun -np 10 test1 400000000
The MPI_Scatter time = 0.702 (23.95% of total)
24.38user 1.37system 0:02.93elapsed 879%CPU 
$ /usr/bin/time mpirun -np 10 test1 1000000000
The MPI_Scatter time = 1.762 (26% of total)
60.17user 2.42system 0:06.62elapsed 944%CPU

Jawaban

1 dreamcrash Jan 03 2021 at 18:43

Yang hanya memberikan keuntungan kinerja sekitar 25%. Dugaan saya di sini adalah bahwa kemacetan mungkin disebabkan oleh proses yang bersaing untuk mengakses memori. (..)

Kode Anda sebagian besar terikat dengan komunikasi dan CPU. Selain itu, menurut hasil Anda untuk 2, 5, dan 10 proses:

 $ /usr/bin/time mpirun -np 2 test1 2000000000 The totalsum = 2000000000 24.05user 3.40system 0:14.03elapsed 195%CPU (0avgtext+0avgdata 11724552maxresident)k 0inputs+960outputs (6major+23195minor)pagefaults 0swaps $ /usr/bin/time mpirun -np 5 test1 2000000000
The totalsum = 2000000000
55.27user 3.54system 0:12.88elapsed 456%CPU (0avgtext+0avgdata 9381132maxresident)k 0inputs+4512outputs (26major+31614minor)pagefaults 0swaps

$ /usr/bin/time mpirun -np 10 test1 2000000000
The totalsum = 2000000000
106.43user 4.07system 0:12.44elapsed 887%CPU (0avgtext+0avgdata 8599952maxresident)k 0inputs+8720outputs (51major+50059minor)pagefaults 0swaps

Kode sudah berhenti menskalakan di sekitar lima proses, tidak mungkin (pada titik ini) untuk lebar-terikat memori menjadi jenuh.

Kemudian saya mencoba yang sama tetapi tanpa menggunakan memori untuk mendapatkan data. (..) Ini menunjukkan sekitar 83% perolehan kinerja dan akan mengkonfirmasi dugaan saya.

Tapi Anda juga menghapus MPI_Scatterpanggilan itu. Akibatnya, mengurangi overhead komunikasi, sekaligus menjaga jumlah pekerjaan yang pada dasarnya sama untuk dilakukan secara paralel.

Saya telah membuat profil kode Anda di mesin saya (2 inti fisik; 4 logis). Untuk mengukur waktu, saya menggunakan MPI_Wtime();sebagai berikut:

int main(int argc, char **argv)
{
        int mpirank, mpisize;
        int tabsize = atoi(*(argv + 1));

        MPI_Init(&argc, &argv);
        MPI_Comm_rank(MPI_COMM_WORLD, &mpirank);
        MPI_Comm_size(MPI_COMM_WORLD, &mpisize);

        MPI_Barrier(MPI_COMM_WORLD);
        double start = MPI_Wtime();
        ...
                if(mpirank == 0){
                printf("The totalsum = %li\n", totalsum);
        }
        MPI_Barrier(MPI_COMM_WORLD);
        double end = MPI_Wtime();
        if(mpirank == 0)
          printf("Time:%f\n",end-start);
}

untuk masukan yang sama dengan Anda ( yaitu, 2000000000) hasilnya adalah:

1 process : 25.158740 seconds
2 processes : 19.116490 seconds
4 processes : 15.971734 seconds 

Peningkatan sekitar 40% dan hierarki memori mesin saya harus jauh lebih rendah daripada mesin dengan 20 inti fisik.

Mari kita sekarang secara signifikan mengurangi ukuran input, sehingga mengurangi jejak memori, dari 2000000000 (8 gigabyte) menjadi hanya 250000000 (1 gigabyte), dan menguji ulang lagi:

1 process : 1.312354 seconds
2 processes : 1.229174 seconds
4 processes : 1.232522 seconds 

Peningkatan sekitar 6%; Jika kemacetan adalah proses yang bersaing untuk mendapatkan memori, saya tidak akan mengharapkan pengurangan seperti itu dalam percepatan setelah mengurangi jejak memori. Meskipun demikian, pengurangan ini dapat dengan mudah dijelaskan oleh fakta bahwa dengan mengurangi ukuran input I meningkatkan rasio komunikasi per komputasi.

Mari kita kembali ke tes dengan 2000000000 elemen tetapi kali ini mengukur waktu yang dihabiskan untuk MPI_Scatterrutinitas komunikasi (yang telah Anda hapus):

2 processes : 7.487354 seconds
4 processes : 8.728969 seconds 

Seperti yang dapat dilihat dengan proses 2 dan 4, kira-kira 40% ( yaitu, 7.487354 / 19.116490) dan 54% ( yaitu, 8.728969 / 15.971734) dari waktu eksekusi aplikasi dihabiskan MPI_Scattermasing-masing sendirian. Itulah mengapa, ketika Anda menghapus rutinitas itu, Anda telah memperhatikan peningkatan kecepatan.

Sekarang tes yang sama untuk input 250000000 (1 gigabyte):

2 processes ::0.679913 seconds (55% of the time)
4 processes : 0.691987 seconds (56% of the time)

Seperti yang Anda lihat, bahkan dengan footprint memori yang lebih kecil, overhead dari MPI_scatterpersentase tetap sama (untuk 4 proses). Kesimpulannya adalah bahwa semakin banyak proses, semakin sedikit komputasi per proses, dan akibatnya, semakin tinggi rasio komunikasi per komputasi - tidak termasuk overhead lain yang mungkin muncul dengan jumlah proses yang berjalan lebih tinggi. Selain itu, dalam kode Anda, dengan lebih banyak proses, penggunaan memori tidak tumbuh linier, kecuali untuk proses utama (yang berisi seluruh data) proses reaming akan membuat data tersebar di antara mereka.

Biasanya, MPI_scatterimplementasi yang baik , akan memiliki kompleksitas waktu O (n log p) , dengan nukuran input dan pjumlah proses. Oleh karena itu, biaya overhead MPI_scatterakan meningkat lebih cepat dengan meningkatkan ukuran input kemudian dengan meningkatkan jumlah proses yang terlibat dalam komunikasi tersebut. Namun, dengan meningkatkan ukuran input, Anda memiliki lebih banyak komputasi per proses yang dilakukan secara paralel, sedangkan jika Anda meningkatkan jumlah proses, Anda akan memiliki lebih sedikit komputasi per proses yang dilakukan.

Ingatlah, bagaimanapun, bahwa tes yang saya lakukan bukanlah yang paling akurat yang pernah ada, karena lingkungan yang saya jalankan, implementasi MPI saya mungkin berbeda dari Anda, dan seterusnya. Meskipun demikian, saya yakin bahwa jika Anda melakukan pengujian yang sama pada pengaturan Anda, Anda akan menarik kesimpulan yang sama.