Hiệu suất tồi tệ với MPI
Tôi đang tìm hiểu MPI và có câu hỏi về việc hầu như không tăng hiệu suất trong cách triển khai đơn giản bên dưới.
#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;
}
Thời gian thực hiện của việc thực hiện trên là:
$ /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
Điều này chỉ mang lại hiệu suất tăng khoảng 25%. Suy đoán của tôi ở đây là nút cổ chai có thể được gây ra bởi các quá trình cạnh tranh để truy cập bộ nhớ. Sau đó, tôi đã thử tương tự nhưng không sử dụng bộ nhớ để truy cập dữ liệu.
#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;
}
đã cho các kết quả sau:
$ /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
Điều này cho thấy mức tăng hiệu suất khoảng 83% và sẽ xác nhận những suy đoán của tôi. Sau đó, bạn có thể cho tôi biết liệu suy đoán của tôi có đúng không và nếu có, có cách nào để cải thiện việc triển khai đầu tiên với quyền truy cập bộ nhớ không?
Mã đã được chạy trên máy có 20 lõi vật lý.
EDIT1: kết quả bổ sung của lần triển khai đầu tiên cho quy trình 2, 5 và 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:
Tôi đã đặt MPI_Wtime () để đo lường phần MPI_Scatter của lần triển khai đầu tiên như sau:
...
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);
...
và nhận được kết quả sau:
$ /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
Trả lời
Điều này chỉ mang lại hiệu suất tăng khoảng 25%. Suy đoán của tôi ở đây là nút cổ chai có thể được gây ra bởi các quá trình cạnh tranh để truy cập bộ nhớ. (..)
Mã của bạn chủ yếu là giao tiếp và ràng buộc CPU. Hơn nữa, theo kết quả của bạn cho các quy trình 2, 5 và 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
Mã ngừng mở rộng quy mô đã có ở khoảng năm quy trình, không có khả năng (tại thời điểm này) cho độ rộng giới hạn của bộ nhớ bị bão hòa.
Sau đó, tôi đã thử tương tự nhưng không sử dụng bộ nhớ để truy cập dữ liệu. (..) Điều này cho thấy mức tăng hiệu suất khoảng 83% và sẽ xác nhận những suy đoán của tôi.
Nhưng bạn cũng đã xóa MPI_Scatter
cuộc gọi. Do đó, giảm thiểu chi phí liên lạc, trong khi về cơ bản vẫn giữ nguyên khối lượng công việc được thực hiện song song.
Tôi đã lập hồ sơ mã của bạn trong máy của tôi (2 lõi vật lý; 4 lõi logic). Để đo thời gian, tôi đang sử dụng MPI_Wtime();
như sau:
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);
}
đối với đầu vào bằng của bạn ( tức là 2000000000), kết quả là:
1 process : 25.158740 seconds
2 processes : 19.116490 seconds
4 processes : 15.971734 seconds
Cải thiện khoảng 40% và phân cấp bộ nhớ của máy tính của tôi sẽ kém hơn nhiều so với máy có 20 lõi vật lý.
Bây giờ chúng ta hãy giảm đáng kể kích thước đầu vào, do đó giảm dung lượng bộ nhớ, từ 2000000000 (8 gigabyte) xuống chỉ còn 250000000 (1 gigabyte) và kiểm tra lại:
1 process : 1.312354 seconds
2 processes : 1.229174 seconds
4 processes : 1.232522 seconds
Cải thiện khoảng 6%; Nếu nút thắt cổ chai là các quá trình cạnh tranh cho bộ nhớ, tôi sẽ không mong đợi việc giảm tốc độ như vậy sau khi giảm bộ nhớ. Tuy nhiên, sự giảm này có thể được giải thích dễ dàng bởi thực tế là bằng cách giảm kích thước đầu vào, tôi đã tăng tỷ lệ truyền thông trên mỗi phép tính.
Chúng ta hãy quay lại các bài kiểm tra với 2000000000 phần tử nhưng lần này là đo thời gian dành cho MPI_Scatter
thói quen giao tiếp (cái mà bạn đã loại bỏ):
2 processes : 7.487354 seconds
4 processes : 8.728969 seconds
Như người ta có thể thấy với quy trình 2 và 4, khoảng 40% ( tức là 7.487354 / 19.116490) và 54% ( tức là 8.728969 / 15.971734) thời gian thực thi ứng dụng được dành cho MPI_Scatter
một mình tương ứng. Đó là lý do tại sao, khi bạn loại bỏ thói quen đó, bạn đã nhận thấy sự cải thiện về tốc độ.
Bây giờ cùng một bài kiểm tra cho đầu vào 250000000 (1 gigabyte):
2 processes ::0.679913 seconds (55% of the time)
4 processes : 0.691987 seconds (56% of the time)
Như bạn có thể thấy, ngay cả với bộ nhớ nhỏ hơn, chi phí của MPI_scatter
tỷ lệ phần trăm vẫn là như nhau (đối với 4 quy trình). Kết luận là càng nhiều quy trình, thì càng ít tính toán trên mỗi quy trình và do đó, tỷ lệ giao tiếp trên mỗi tính toán càng cao - không bao gồm các chi phí chung khác có thể xuất hiện với số lượng quy trình đang chạy cao hơn. Hơn nữa, trong mã của bạn, với nhiều quy trình hơn, việc sử dụng bộ nhớ không tăng theo tuyến tính, ngoại trừ quy trình chính (chứa toàn bộ dữ liệu), các quy trình tạo lại sẽ có dữ liệu phân tán giữa chúng.
Thông thường, một MPI_scatter
triển khai tốt sẽ có độ phức tạp về thời gian là O (n log p) , với n
kích thước của đầu vào và p
số lượng quy trình. Do đó, chi phí của ý MPI_scatter
chí sẽ tăng nhanh hơn bằng cách tăng kích thước đầu vào sau đó bằng cách tăng số lượng các quy trình liên quan đến giao tiếp đó. Tuy nhiên, bằng cách tăng kích thước đầu vào, bạn có nhiều tính toán hơn cho mỗi quá trình được thực hiện song song, trong khi nếu bạn tăng số lượng quá trình, bạn sẽ có ít tính toán hơn cho mỗi quá trình được thực hiện.
Tuy nhiên, hãy nhớ rằng các thử nghiệm mà tôi đã thực hiện không phải là chính xác nhất từ trước đến nay, do môi trường mà tôi đang chạy, việc triển khai MPI của tôi có thể khác với của bạn, v.v. Tuy nhiên, tôi tin chắc rằng nếu bạn thực hiện các thử nghiệm tương tự trên thiết lập của mình, bạn sẽ đưa ra kết luận tương tự.