MPI ile kötü performans

Jan 03 2021

MPI öğreniyorum ve aşağıdaki basit uygulamada neredeyse hiç performans kazanmadığına dair bir sorum var.

#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;
}

Yukarıdaki uygulamanın yürütme süreleri şunlardır:

$ /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

Bu sadece yaklaşık% 25 performans kazancı sağlar. Buradaki tahminim, darboğazın belleğe erişmek için rekabet eden süreçlerden kaynaklanıyor olabileceğidir. Sonra aynı şeyi denedim, ancak verilere ulaşmak için bellek kullanmadan.

#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;
}

aşağıdaki sonuçları verdi:

$ /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

Bu yaklaşık% 83 performans kazancını gösteriyor ve tahminlerimi doğruluyor. Öyleyse bana tahminlerimin doğru olup olmadığını ve evet ise bellek erişimiyle ilk uygulamayı iyileştirmenin herhangi bir yolu var mı?

Kod, 20 fiziksel çekirdekli makinede çalıştırıldı.

DÜZENLEME1: 2, 5 ve 10 işlem için ilk uygulamanın ek sonuçları:

$ /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

DÜZENLEME2:

İlk uygulamanın MPI_Scatter bölümünü aşağıdaki gibi ölçmek için MPI_Wtime () koydum:

...
                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);
...

ve aşağıdaki sonuçları aldı:

$ /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

Yanıtlar

1 dreamcrash Jan 03 2021 at 18:43

Bu sadece yaklaşık% 25 performans kazancı sağlar. Buradaki tahminim, darboğazın belleğe erişmek için rekabet eden süreçlerden kaynaklanıyor olabileceğidir. (..)

Kodunuz çoğunlukla iletişime ve CPU'ya bağlıdır. Ayrıca 2, 5 ve 10 işlem için sonuçlarınıza göre:

 $ /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

Kod, yaklaşık beş işlemde ölçeklendirmeyi durdurur, bellek sınır genişliğinin doygun olması olası değildir (bu noktada).

Sonra aynı şeyi denedim, ancak verilere ulaşmak için bellek kullanmadan. (..) Bu yaklaşık% 83 performans kazancı gösteriyor ve tahminlerimi doğruluyor.

Ama MPI_Scatteraramayı da kaldırdınız . Sonuç olarak, paralel olarak gerçekleştirilecek temelde aynı miktarda iş tutulurken iletişim ek yükünün azaltılması.

Makinemde kodunuzun profilini çıkardım (2 fiziksel çekirdek; 4 mantıksal). Süreleri ölçmek için MPI_Wtime();aşağıdaki gibi kullanıyorum :

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);
}

sizinkine eşit bir girdi için ( yani, 2000000000) sonuçlar:

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

Yaklaşık% 40'lık bir iyileştirme ve makinemin bellek hiyerarşisi, 20 fiziksel çekirdekli bir makineden çok daha düşük olmalıdır.

Şimdi giriş boyutunu önemli ölçüde küçültelim, böylece bellek ayak izini 2000000000'den (8 gigabayt) sadece 250000000'e (1 gigabayt) düşürelim ve tekrar test edelim:

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

Yaklaşık% 6'lık bir iyileşme; Darboğaz bellek için rekabet eden süreçler olsaydı, bellek ayak izini azalttıktan sonra hızlanmalarda böyle bir azalma beklemiyordum. Bununla birlikte, bu azalma, girdi boyutunu düşürerek hesaplama başına iletişim oranını arttırdığım gerçeğiyle kolayca açıklanabilir .

2000000000 elemanlı testlere geri dönelim, ancak bu sefer MPI_Scatteriletişim rutininde (çıkardığınız) harcanan zamanı ölçerek :

2 processes : 7.487354 seconds
4 processes : 8.728969 seconds 

2 ve 4 işlemle görülebileceği gibi, uygulama yürütme süresinin yaklaşık% 40'ı ( yani, 7.487354 / 19.116490) ve% 54'ü ( yani 8.728969 / 15.971734) MPI_Scattersırasıyla tek başına harcandı . Bu nedenle, bu rutini kaldırdığınızda, hızlanmada bir iyileşme fark ettiniz.

Şimdi 250000000 (1 gigabayt) girişi için aynı test:

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

Gördüğünüz gibi, daha küçük bir bellek ayak iziyle bile, kalan ek yük MPI_scatteryüzde olarak aynı (4 işlem için). Sonuç şudur ki, daha fazla işlem, işlem başına daha az hesaplama ve sonuç olarak, daha fazla sayıda işlemin çalıştığı diğer genel giderler hariç olmak üzere, hesaplama başına iletişim oranı o kadar yüksek olur. Dahası, kodunuzda, daha fazla işlemle birlikte bellek kullanımı doğrusal olarak artmaz, ana işlem (tüm verileri içeren) dışında, raybalama işlemleri veri dağılımına sahip olur.

Tipik olarak, iyi bir MPI_scatteruygulama, girdi boyutu ve işlem sayısı ile O (n log p) bir zaman karmaşıklığına sahip olacaktır . Bu nedenle, girdi boyutunu artırarak ve ardından bu iletişimle ilgili süreçlerin sayısını artırarak ek yük daha hızlı artacaktır. Bununla birlikte, girdi boyutunu artırarak paralel olarak gerçekleştirilen işlem başına daha fazla hesaplamaya sahip olursunuz , oysa işlemlerin sayısını artırırsanız gerçekleştirilen işlem başına daha az hesaplamaya sahip olursunuz .npMPI_scatter

Bununla birlikte, yaptığım testlerin şimdiye kadarki en doğru test olmadığını, çalıştırdığım ortam nedeniyle, MPI uygulamamın sizinkinden farklı olabileceğini ve bunun gibi şeyleri aklınızda bulundurun. Yine de, kurulumunuzda aynı testleri yaparsanız, aynı sonuçları çıkaracağınıza eminim.