MPI के साथ गंभीर प्रदर्शन

Jan 03 2021

मैं MPI सीख रहा हूं और नीचे दिए गए सरल कार्यान्वयन में लगभग कोई प्रदर्शन लाभ नहीं होने का प्रश्न है।

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

उपरोक्त कार्यान्वयन के निष्पादन समय हैं:

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

जो केवल 25% प्रदर्शन लाभ देता है। मेरा अनुमान यहाँ है कि अड़चन उन प्रक्रियाओं के कारण हो सकती है जो स्मृति तक पहुँचने के लिए प्रतिस्पर्धा करती हैं। फिर मैंने वही कोशिश की लेकिन डेटा प्राप्त करने के लिए मेमोरी का उपयोग किए बिना।

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

जो निम्नलिखित परिणाम दिया:

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

यह 83% प्रदर्शन लाभ के बारे में दिखाता है और मेरे अनुमानों की पुष्टि करेगा। तब क्या आप मुझे बता सकते हैं कि क्या मेरे अनुमान सही हैं और यदि हाँ, मेमोरी एक्सेस के साथ पहले कार्यान्वयन को बेहतर बनाने के लिए कोई उपाय हैं?

कोड को 20 भौतिक कोर के साथ मशीन पर चलाया गया है।

EDIT1: 2, 5 और 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:

मैंने MPI_WIME () को MPI_Scatter के पहले कार्यान्वयन के भाग को मापने के लिए रखा है:

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

और निम्नलिखित परिणाम मिले:

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

जवाब

1 dreamcrash Jan 03 2021 at 18:43

जो केवल 25% प्रदर्शन लाभ देता है। मेरा अनुमान यहाँ है कि अड़चन उन प्रक्रियाओं के कारण हो सकती है जो स्मृति तक पहुँचने के लिए प्रतिस्पर्धा करती हैं। (..)

आपका कोड अधिकतर संचार- और CPU- बाध्य है। इसके अलावा, 2, 5 और 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

कोड लगभग पाँच प्रक्रियाओं पर पहले से ही स्केलिंग बंद कर देता है, इस सीमा पर (इस बिंदु पर) स्मृति बाउंड-संतृप्त होने की संभावना नहीं है।

फिर मैंने वही कोशिश की लेकिन डेटा प्राप्त करने के लिए मेमोरी का उपयोग किए बिना। (..) यह 83% प्रदर्शन लाभ के बारे में दिखाता है और मेरे अनुमानों की पुष्टि करेगा।

लेकिन आपने MPI_Scatterकॉल भी हटा दिया । नतीजतन, संचार ओवरहेड को कम करना, जबकि मूल रूप से समान कार्य को समान रूप से निष्पादित करना है।

मैंने आपके कोड को अपनी मशीन (2 भौतिक कोर; 4 तार्किक) में बदल दिया है। समय को मापने के लिए, मैं MPI_Wtime();निम्नानुसार उपयोग कर रहा हूं :

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

आपके लिए इनपुट के बराबर ( यानी, 2000000000) परिणाम थे:

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

लगभग 40% का सुधार और मेरी मशीन की मेमोरी-पदानुक्रम 20 भौतिक कोर वाली मशीन के लिए बहुत अधिक हीन होना चाहिए।

आइए अब हम इनपुट साइज़ को महत्वपूर्ण रूप से कम करते हैं, इसलिए मेमोरी फ़ुटप्रिंट को कम करते हुए, 2000000000 (8 गीगाबाइट) से सिर्फ 250000000 (1 गीगाबाइट) तक घटाते हैं, और फिर से पुन: प्राप्त करते हैं:

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

लगभग 6% का सुधार; यदि अड़चन स्मृति के लिए प्रतिस्पर्धा करने वाली प्रक्रियाएं थीं, तो मैं मेमोरी पदचिह्न को कम करने के बाद स्पीडअप में इस तरह की कमी की उम्मीद नहीं करूंगा। बहरहाल, इस कमी को इस तथ्य से आसानी से समझाया जा सकता है कि इनपुट आकार को कम करके मैंने प्रति गणना संचार के अनुपात को बढ़ाया ।

आइए हम 2000000000 तत्वों के साथ परीक्षणों पर वापस जाते हैं, लेकिन इस बार MPI_Scatterसंचार दिनचर्या (आपके द्वारा हटाए गए समय) पर खर्च किए गए समय को मापने के लिए :

2 processes : 7.487354 seconds
4 processes : 8.728969 seconds 

जैसा कि 2 और 4 प्रक्रियाओं के साथ देख सकते हैं, लगभग 40% ( यानी, 7.487354 / 19.116490) और 54% ( यानी, 8.728969 / 15.971734) आवेदन निष्पादन समय MPI_Scatterक्रमशः अकेले पर खर्च किया गया था । इसीलिए, जब आपने उस रूटीन को हटा दिया, तो आपने स्पीडअप में सुधार देखा है।

अब इनपुट 250000000 (1 गीगाबाइट) के लिए एक ही परीक्षण:

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

जैसा कि आप देख सकते हैं, यहां तक ​​कि एक छोटी सी स्मृति पदचिह्न के साथ, MPI_scatterसमान (4 प्रक्रियाओं के लिए) के आसपास प्रतिशत प्रतिशत के ओवरहेड । निष्कर्ष यह है कि अधिक प्रक्रियाएं, प्रति प्रक्रिया कम गणना , और परिणामस्वरूप, प्रति गणना संचार का अनुपात अधिक है - अन्य ओवरहेड्स को छोड़कर जो चल रही अधिक संख्या में पॉपअप हो सकते हैं। इसके अलावा, आपके कोड में, अधिक प्रक्रियाओं के साथ मेमोरी उपयोग रैखिक नहीं बढ़ता है, मुख्य प्रक्रिया को छोड़कर (जिसमें संपूर्ण डेटा होता है) रीमिंग प्रक्रियाओं में डेटा बिखराव होगा।

आमतौर पर, एक अच्छा MPI_scatterकार्यान्वयन, का एक समय जटिलता होगा O (n लॉग ऑन पी) , के साथ nइनपुट के आकार और किया जा रहा है pप्रक्रियाओं की संख्या। इसलिए, MPI_scatterइनपुट के आकार को बढ़ाकर और फिर उस संचार में शामिल प्रक्रियाओं की संख्या बढ़ाकर वसीयत का ओवरहेड तेजी से बढ़ेगा। हालाँकि, इनपुट आकार में वृद्धि करके आपको समानांतर में प्रदर्शन की जाने वाली प्रक्रिया के प्रति अधिक संगणना होती है, जबकि यदि आप प्रक्रियाओं की संख्या बढ़ाते हैं, तो आपके द्वारा निष्पादित की जाने वाली प्रक्रिया के प्रति कम संगणना होगी ।

हालांकि, यह ध्यान में रखते हुए कि मैंने जो परीक्षण किए हैं, वे अब तक के सबसे सटीक नहीं हैं, क्योंकि मैं जो पर्यावरण चला रहा हूं, मेरा एमपीआई कार्यान्वयन आपके से भिन्न हो सकता है, और इसी तरह। फिर भी, मुझे विश्वास है कि यदि आप अपने सेटअप पर समान परीक्षण करते हैं, तो आप समान निष्कर्ष निकालेंगे।