8 3 Hàm MPI_Comm_size

Một phần của tài liệu LUẬN VĂN: NGHIÊN CỨU HỆ THỐNG CỤM MÁY TÍNH XỬ LÝ SONG SONG ROCKS VÀ ỨNG DỤNG pptx (Trang 77 - 186)

Hàm này đƣợc dùng để lấy số lƣợng tiến trình đang chạy trong nhóm làm việc chứa tiến trình hiện tại. Lƣu ý rằng kết quả trả về là số lƣợng tiến trình đang chạy chứ không phải số lƣợng máy con trong cluster đang đƣợc sử dụng. Hàm này chấp nhận 2 tham số, tham số đầu là tên của bộ liên lạc (Communicator) (mặc định là MPI_COMM_WORLD) (khái niệm bộ liên lạc sẽ đƣợc giải thích bên dƣới), tham số địa chỉ thứ 2 là biến để lƣu kết quả trả về. Bộ liên lạc MPI_COMM_WORLD bao gồm tất cả các tiến trình sẵn có trong quá trình khởi tạo. Bộ liên lạc đƣợc sử dụng để phân biệt cũng nhƣ giao tiếp giữa các tiến trình, do đó nó cung cấp một phƣơng tiện để "đóng gói" phần hạ tầng giao tiếp của MPI. Mặc dù có thể tạo ra một bộ liên lạc khác nhƣng điều này nói chung không cần thiết, do bộ liên lạc mặc định đã đáp ứng đủ nhu cầu của chúng ta.

4. 8. 4. Hàm MPI_Comm_rank

Hàm này đƣợc dùng để xác định rank (hạng) của tiến trình hiện tại trong bộ liên lạc. Ý nghĩa của các tham số tƣơng tự nhƣ MPI_Comm_size. Khi khởi tạo các tiến trình, mỗi tiến trình sẽ đƣợc gán một rank tƣơng ứng trong bộ liên lạc, đó là một con số nằm trong

66

khoảng từ 0 đến n - 1, với n là số lƣợng tiến trình. Chẳng hạn trong ví dụ trên ngƣời quản trị yêu cầu 5 tiến trình, do đó rank của các tiến trình sẽ là 0, 1, 2, 3, 4. Do mỗi tiến trình thực chất là việc thực thi của cùng một chƣơng trình trên các máy con khác nhau, nên để biết đƣợc tiến trình hiện tại là tiến trình nào, tiến trình hiện tại cần phải dựa vào rank tƣơng ứng của nó. Theo thông lệ, tiến trình ứng với rank 0 sẽ chịu trách nhiệm lấy kết quả trả về từ các tiến trình khác và tiếp tục xử lý. Việc kết quả trả về từ mỗi tiến trình là nhanh hay chậm nói chung không hoàn toàn đồng nghĩa với sức mạnh xử lý của mỗi máy con tƣơng ứng, vì nó còn phụ thuộc vào tốc độ của các kết nối giữa các máy con.

4. 10. Giá trị trả về của các hàm MPI

Hầu hết các hàm MPI đều trả về một số nguyên, cho biết trạng thái thực thi của hàm đó. Giá trị trả về là gì và ý nghĩa của chúng nhƣ thế nào phụ thuộc vào các bản cài đặt khác nhau của MPI [24]. Tuy nhiên, có một giá trị trả về đƣợc chuẩn hóa, đó là MPI_SUCCESS, cho biết hàm thực thi thành công. Nếu giá trị trả về của một hàm không phải là MPI_SUCCESS thì đó là một mã lỗi. Từ mã lỗi này, ngƣời lập trình có thể lấy đƣợc thông tin lỗi nhờ hàm MPI_Error_string.

4. 11. Về các khái niệm Nhóm, Ngữ cảnh và Bộ liên lạc

Một nhóm (Group) là một tập có thứ tự của các tiến trình [10]. Mỗi tiến trình trong nhóm có một chỉ số duy nhất gọi là rank (hạng). Rank của một tiến trình có giá trị từ 0

đến p – 1, với p là số tiến trình trong nhóm.

Ngữ cảnh (Context) là một khái niệm trừu tƣợng, hàm ý mỗi thông điệp “biết” về bộ liên lạc của nó [32]. Ngữ cảnh ra đời nhằm chia tách các họ thông điệp, tránh tình trạng một thông điệp đƣợc gửi với một bộ liên lạc này lại đƣợc nhận với một bộ liên lạc khác. Nếu một thông điệp đƣợc truyền từ tiến trình thứ nhất và đƣợc nhận bởi tiến trình thứ hai, thì hai tiến trình này phải thuộc cùng một bộ liên lạc; khi đó ta nói rằng thông điệp đó đã đƣợc gửi và nhận trong cùng ngữ cảnh.

Một bộ liên lạc (Communicator) bao gồm một nhóm các tiến trình và một ngữ cảnh [32]. Các tiến trình nếu muốn gửi và nhận thông điệp của nhau thì bắt buộc phải thuộc cùng một bộ liên lạc, và ngƣợc lại, khi 2 tiến trình thuộc cùng một bộ liên lạc thì chúng

67

có thể truyền thông điệp cho nhau. Trong môi trƣờng MPI, mỗi tiến trình phải thuộc ít nhất một bộ liên lạc nào đó. Một tiến trình có thể thuộc nhiều hơn một bộ liên lạc.

Mỗi bộ liên lạc có một ngữ cảnh duy nhất tƣơng ứng với nó, và mỗi ngữ cảnh chỉ tƣơng ứng với duy nhất một bộ liên lạc [10].

Khi một chƣơng trình MPI bắt đầu chạy, có một nhóm sơ cấp đƣợc khởi tạo bao gồm tất cả các tiến trình. Tƣơng ứng với nó là bộ liên lạc sơ cấp MPI_COMM_WORLD. Sau khi ngƣời dùng gọi hàm MPI_Init, có một bộ liên lạc đặc biệt khác đƣợc tạo là MPI_COMM_SELF [20]. Đây là bộ liên lạc chỉ chứa duy nhất 1 tiến trình hiện tại.

Ngoài MPI_COMM_WORLD và MPI_COMM_SELF còn có một số object đặc biệt khác nhƣ MPI_COMM_NULL, …, tuy nhiên đây không phải là các bộ liên lạc đặc biệt ([21, 34, 35]).

Về khái niệm liên lạc trong (inter-communication), liên lạc ngoài (intra- communication), bộ liên lạc trong (inter-communicator), bộ liên lạc ngoài (intra- communicator), tham khảo [27, 28].

4. 12. Liên lạc giữa các tiến trình

Có thể liên lạc giữa các tiến trình trong MPI theo 2 cách: Liên lạc 2 điểm (Point – To – Point Communication) và liên lạc đa điểm (Collective Communication). Liên lạc điểm nối điểm lại đƣợc phân chia thành liên lạc đồng bộ (Blocking Communication) và liên lạc bất đồng bộ (Non – Blocking Communication).

Có rất nhiều hàm MPI đƣợc cài đặt để phục vụ cho việc liên lạc giữa các tiến trình. Do tài liệu chỉ tập trung vào việc giới thiệu về MPI để phục vụ cho lập trình song song ứng dụng cho Rocks, nên ở đây ta chỉ bàn đến 2 hàm liên lạc phổ biến nhất của MPI, thuộc loại liên lạc đồng bộ là MPI_Send và MPI_Recv.

Hàm MPI_Send

int MPI_Send(void *address, int count, MPI_Datatype dataType, int destination, int tag, MPI_Comm communicator)

68

 (address, count, dataType) định nghĩa count lần xuất hiện của dữ liệu dạng dataType bắt đầu từ địa chỉ address.

 destination là rank của tiến trình nhận trong nhóm tƣơng ứng với communicator.

 tag là một số nguyên sử dụng để phân loại thông điệp

 communicator định nghĩa một nhóm các tiến trình và ngữ cảnh của chúng. Hàm MPI_Recv

int MPI_Recv(void *address, int maxCount, MPI_Datatype dataType, int source, int tag, MPI_Comm communicator, MPI_Status *status)

Trong đó:

 (address, maxCount, dataType) định nghĩa bộ đệm nhận tƣơng tự nhƣ trong MPI_Send. Toán tử này cho phép một lƣợng ít hơn hoặc bằng maxCount dữ liệu dạng dataType đƣợc nhận. Các tham số tag và communicator có ý nghĩa tƣơng tự nhƣ trong MPI_Send, tuy nhiên có một điểm khác biệt là chấp nhận giá trị đại diện. (adsbygoogle = window.adsbygoogle || []).push({});

 source là hạng của nguồn gửi thông điệp trong nhóm tƣơng ứng với communicator, hoặc một giá trị đại diện cho bất kỳ nguồn nào.

 status lƣu giữ thông tin về kích thƣớc thực sự của thông điệp, nguồn gửi thông điệp thực sự và thẻ thông điệp thực sự. Sở dĩ cần có một biến lƣu giữ các giá trị “thực sự” là vì thông điệp nhận đƣợc có thể có kích thƣớc nhỏ hơn kích thƣớc xác định bởi maxCount, và các tham số source và tag có thể chấp nhận giá trị đại diện.

Chúng ta có nhận xét:

 3 giá trị source, destination và communicator xác định nhóm, còn 2 giá trị tag và communicator xác định ngữ cảnh của cả 2 tiến trình gửi và nhận.

 Bộ tham số (address, maxCount, dataType) xác định thông điệp cần gửi. Sự linh hoạt của bộ tham số này thể hiện trong tham số thứ 3, dataType. Tham số này nhận giá trị là các kiểu dữ liệu cơ sở của ngôn ngữ cài đặt. Ví dụ, (A, 300, MPI_REAL) định nghĩa một vectơ A bao gồm 300 thành phần là các số thực trong

69

Fortran, không cần biết đến chiều dài hay định dạng của các số này. Một bản cài đặt MPI dành cho các mạng hỗn tạp đảm bảo rằng máy tính chủ của tiến trình nhận phải nhận đƣợc chính xác 300 thành phần thực này mà ngữ nghĩa hay giá trị không thay đổi, mặc dù máy tính chủ của tiến trình nhận có thể có kiến trúc khác (và do đó, khác nhau về định dạng số thực) với máy tính chủ của tiến trình nhận. Ở mức cao nhất, sức mạnh của mô hình mới này nằm ở khả năng ngƣời dùng có thể định nghĩa các kiểu dữ liệu mới và các kiểu dữ liệu này có thể mô tả các dữ liệu không liên tục.

MPI linh hoạt trong việc định nghĩa thông điệp cần gửi là do các lý do sau đây:  Thƣờng thì dữ liệu cần gửi là dữ liệu rời rạc, theo nghĩa không tồn tại trong một

hoặc nhiều vùng nhớ liên tiếp. Một ví dụ đơn giản chính là khi ta cần gửi đi dữ liệu về một cột của ma trận, nhƣng ma trận lại hướng hàng, nghĩa là các phần tử của ma trận đƣợc lƣu trữ theo hàng. Tổng quát hơn, dữ liệu cần gửi có thể là tập hợp của các cấu trúc dữ liệu có kích thƣớc khác nhau. Các thƣ viện cổ điển cung cấp các phƣơng pháp để “đóng gói” dữ liệu trong bộ đệm một cách liên tục tại đầu gửi và “tháo gói” tại đầu nhận. Tuy nhiên, sẽ tốt hơn về mặt hiệu năng nếu nhƣ việc đóng gói đƣợc thực hiện trong quá trình gửi, thay vì phải có thêm một bƣớc trƣớc khi gửi để đóng gói dữ liệu.

 Sự phát triển của tính toán hỗn tạp (Heterogeneous Computing, [25, 26]). Tính toán hỗn tạp phát triền do nhu cầu cần phân bố các thành phần của các tác vụ tính toán ra các máy tính bán chuyên biệt nhƣ các dòng máy SIMD, vectơ hay graphics; và nhu cầu sử dụng các mạng máy trạm (Workstation Network) nhƣ các máy song song. Các mạng máy trạm thƣờng bao gồm các máy tính đƣợc tích hợp qua thời gian, do đó thƣờng không đồng nhất về kiến trúc. Khi xuất hiện nhu cầu trao đổi thông điệp giữa các máy tính khác nhau về mặt kiến trúc, thì cần phải có một bộ tham số tổng quát để mô tả phù hợp cho ngữ nghĩa của thông điệp cần gửi. Ví dụ, với một vectơ gồm các số dấu chấm động, không chỉ định dạng mà ngay cả chiều dài của các số dấu chấm động cũng khác nhau với các kiến trúc máy khác nhau. Tƣơng tự, điều này cũng đúng trong trƣờng hợp các số nguyên. Trong quá trình gửi và nhận thông điệp, thƣ viện đƣợc sử dụng có thể tiến hành các chuyển

70

đổi cần thiết nhƣng chỉ khi chúng xác định đƣợc một cách chính xác cái gì đang đƣợc gửi đi.

Một trong những điểm cần lƣu ý khi sử dụng cặp hàm MPI_Send và MPI_Recv đó là tính chất đồng bộ. Tác vụ gửi đƣợc xem là hoàn thành nếu nhƣ thông điệp gửi đi đã đƣợc nhận bởi một tiến trình nào đó. Tác vụ nhận đƣợc xem là hoàn thành nếu nhƣ có một thông điệp đến phù hợp với ngữ cảnh của tiến trình nhận, và tiến trình nhận nhận thành công thông điệp đó. Các hàm này không kết thúc cho đến khi các tác vụ gửi và nhận tƣơng ứng của chúng hoàn thành. Vì thế, nếu tất cả các tiến trình đều gọi cặp hàm này theo thứ tự:

MPI_Send(data, destination); MPI_Recv(newdata, source);

Thì ứng dụng sẽ bị rơi vào trạng thái deadlock (treo). Nguyên nhân là không có tiến trình nào nhận các thông điệp đƣợc gửi đi, vì vậy tất cả các tác vụ gửi đều chƣa hoàn thành, ứng dụng sẽ không thoát đƣợc.

Để tránh trƣờng hợp ứng dụng bị rơi vào trạng thái deadlock, nên thiết kế sao cho mỗi tác vụ gửi phải có một tác vụ nhận tƣơng ứng, giống nhƣ tính chất đồng bộ của cặp hàm MPI_Send và MPI_Recv.

if (rank % 2) {

MPI_Send(data, (rank + 1) % size); MPI_Recv(newdata, (rank - 1) % size); } else {

MPI_Recv(newdata, (rank - 1) % size); MPI_Send(data, (rank + 1) % size); }

Về phần này, có thể xem thêm phụ lục 2.

4. 13. Xây dựng một ứng dụng tự trị

Một trong những vấn đề khó khăn khi thiết kế các ứng dụng tính toán song song là phân chia công việc giữa các tiến trình nhƣ thế nào để tận dụng đƣợc tài nguyên tính toán. Một ứng dụng ở mức đơn giản sẽ đƣợc thiết kế sao cho các tiến trình tham gia tính

71

toán biết trƣớc về phần công việc mà mình cần làm. Chẳng hạn, với ứng dụng tính tích phân (xem mã nguồn đi kèm), thì mỗi máy sẽ đảm nhiệm khối lƣợng công việc, với n

là số lƣợng máy tham gia tính toán. Trên thực tế các ứng dụng xử lý song song không gặp thuận lợi nhƣ vậy. Máy chủ (là máy truyền dữ liệu đến các máy con và thu thập kết quả tính toán từ các máy con) thƣờng không tham gia tính toán, nó chịu trách nhiệm điều phối công việc giữa các máy con. Các máy con lại có thể có tài nguyên phong phú ở mức độ khác nhau, thời điểm nhận các dữ liệu đầu vào cũng khác nhau, nên sẽ có máy hoàn thành công việc trƣớc các máy còn lại. Khi đó, máy này sẽ ở trạng thái “rỗi”. Để tận dụng tài nguyên từ những máy nhu thế này, các ứng dụng xử lý song song thƣờng đƣợc thiết kế sao cho máy chủ sẽ giao cho mỗi máy con một phần việc nhƣ nhau; mỗi khi có một máy con thông báo cho máy chủ về kết quả phần việc của mình, thì máy chủ lại tiếp tục gửi một phần việc khác cho nó…

Ứng dụng dƣới đây sẽ tính tích của một ma trận với một vectơ. Do chỉ mang tính minh họa, các phần tử của ma trận và vectơ đều đƣợc gán là 1. Mỗi tiến trình con chịu trách nhiệm tính toán một phần tử của ma trận kết quả. Khi một tiến trình con hoàn thành công việc, nó thông báo cho tiến trình chủ biết. Tiến trình chủ tiếp tục giao phần việc tiếp theo cho nó, cho đến khi toàn bộ tác vụ tính toán hoàn thành. Do tính chất “giao việc khi rỗi” này mà ta sẽ đặt tên cho ứng dụng là “tự trị”.

#include "mpi.h" #include "stdio.h"

/*

* Ung dung nhan ma tran - vecto */

int main(int argc, char* argv[]) { (adsbygoogle = window.adsbygoogle || []).push({});

// So hang, so cot toi da int MAX_ROWS = 1000; int MAX_COLS = 1000;

// Dau hieu ket thuc

72

// So hang, so cot thuc te int rows, cols;

// Ma tran

double a[MAX_ROWS][MAX_COLS];

// Mot hang cua ma tran. May con su dung bien nay de luu gia tri // hang ma tran nhan duoc.

double b[MAX_COLS]; // Ket qua

double c[MAX_ROWS];

// Bo nho dem. May chu dung de luu hang sap gui, may con dung de // luu vecto.

double buffer[MAX_COLS];

double ans;

int myid, master, numprocs, ieer; MPI_Status status;

int i, j, numsent, sender; int anstype, row;

// So hang cua ma tran se duoc gui di moi lan for. int roundsize;

MPI_Init(&argc, &argv);

ieer = MPI_Comm_size(MPI_COMM_WORLD, &numprocs); ieer = MPI_Comm_rank(MPI_COMM_WORLD, &myid);

// Khoi tao ma tran va vecto. master = 0; rows = 100; cols = 100; roundsize = numprocs - 1; if (roundsize > rows) { roundsize = rows;

73

}

if (myid == master) {

// --- // Phan code may chu

// ---

for (j = 0; j < cols; ++j) { b[j] = 1;

for (i = 0; i < rows; ++i) { a[i][j] = 1;

} }

numsent = 0;

// Gui vecto

ieer = MPI_Bcast(b, cols, MPI_DOUBLE, master, MPI_COMM_WORLD);

// Gui lan luot cac hang, cho den khi khong con hang nao de // gui, hoac khong con may con nao de gui. (adsbygoogle = window.adsbygoogle || []).push({});

for (i = 1; i <= roundsize; ++i) { for (j = 0; j < cols; ++j) { buffer[j] = a[i][j]; }

ieer = MPI_Send(buffer, cols, MPI_DOUBLE, i, numsent, MPI_COMM_WORLD);

++numsent; }

for (i = 0; i < rows; ++i) {

// Ket qua

ieer = MPI_Recv(&ans, 1, MPI_DOUBLE, MPI_ANY_SOURCE, MPI_ANY_TAG, MPI_COMM_WORLD, &status);

sender = status.MPI_SOURCE; anstype = status.MPI_TAG; c[anstype] = ans;

74

// Co mot may con gui ket qua => Them 1 may con roi. Neu // con du lieu => Gui cho may con roi nay.

if (numsent < rows) {

for (j = 0; j < cols; ++j) { buffer[j] = a[numsent][j]; }

ieer = MPI_Send(buffer, cols, MPI_DOUBLE, sender, numsent, MPI_COMM_WORLD);

++numsent;

Một phần của tài liệu LUẬN VĂN: NGHIÊN CỨU HỆ THỐNG CỤM MÁY TÍNH XỬ LÝ SONG SONG ROCKS VÀ ỨNG DỤNG pptx (Trang 77 - 186)