ประสิทธิภาพที่ไม่ดีด้วย 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 คอร์จริง

แก้ไข 1:ผลลัพธ์เพิ่มเติมของการใช้งานครั้งแรกสำหรับกระบวนการ 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

แก้ไข 2:

ฉันได้ใส่ MPI_Wtime () เพื่อวัดส่วน 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 log p)โดยมีnขนาดของอินพุตและpจำนวนกระบวนการ ดังนั้นค่าใช้จ่ายของMPI_scatterพินัยกรรมจึงเพิ่มขึ้นเร็วขึ้นโดยการเพิ่มขนาดอินพุตจากนั้นเพิ่มจำนวนกระบวนการที่เกี่ยวข้องกับการสื่อสารนั้น อย่างไรก็ตามการเพิ่มขนาดอินพุตทำให้คุณมีการคำนวณต่อกระบวนการที่ดำเนินการควบคู่กันมากขึ้นในขณะที่หากคุณเพิ่มจำนวนกระบวนการคุณจะมีการคำนวณน้อยลงต่อกระบวนการที่ดำเนินการ

อย่างไรก็ตามโปรดจำไว้ว่าการทดสอบที่ฉันทำนั้นไม่แม่นยำที่สุดเท่าที่เคยมีมาเนื่องจากสภาพแวดล้อมที่ฉันกำลังใช้งานการใช้งาน MPI ของฉันอาจแตกต่างจากของคุณและอื่น ๆ อย่างไรก็ตามฉันมั่นใจว่าหากคุณทำการทดสอบเดียวกันกับการตั้งค่าของคุณคุณจะได้ข้อสรุปเดียวกัน