Nhu cầu tính toán trong lĩnh vực khoa học và công nghệ ngày càng cao và trở thành một thách thức vì phương pháp xử lý tuần tự với một bộ xử lý không thể đáp ứng được. Trong suốt nhiều thập kỉ qua, những tiến bộ về Khoa học và Công nghệ đã mang đến những thay đổi lớn trên nhiều lĩnh vực và đóng góp một tầm ảnh hưởng vô cùng quan trọng tới mọi mặt đời sống của con người. Một trong những tiến bộ đó có thể nhắc tới việc các thiết bị phần cứng ngay nay đang trở nên hoàn hảo và đáp ứng được khả năng tính toán mạnh mẽ trước những yêu cầu phức tạp mà con người đề ra. Với sự phát triển mạnh mẽ của công nghệ sản xuất chip, sản xuất phần cứng máy tính thì giờ đây việc xây dựng các hệ thống máy tính mạnh đã trở nên đơn giản hơn trong lĩnh vực tính toán hiệu năng cao. Ngoài ra thách thức đặt ra trong lĩnh vực tính toán hiệu năng cao phải đối mặt là làm sao để sử dụng các hệ thống tính toán hiệu năng cao để giải quyết các bài toán trong đời sống. Nhóm chúng em thực hiện nghiên cứu chủ đề “Đánh giá hiệu năng cao thuật toán tính tích ma trận Fox sử dụng MPI” với bài toán tính tích ma trận khá phổ biến trong xử lý ảnh và trong lĩnh vực AI. Chúng em cũng không quên gửi lời cảm ơn thầy Hà Mạnh Đào đã hỗ trợ nhóm thực hiện đề tài. Nhóm mong muốn qua việc nghiên cứu chủ đề có thể hiểu thêm về các khái niệm trong tính toán hiệu năng cao một lĩnh vực có thể coi là xu hướng hiện nay trong giới công nghệ nói chung từ đó có thể có nền tảng vững chắc để ứng dụng kiến thức đã học vào phát triển các giải pháp thực tế.
Giới thiệu về tính toán hiệu năng cao
Tính toán hiệu năng cao là gì?
Tính toán hiệu năng cao là việc kết hợp sức mạnh của nhiều máy tính để đạt được kết quả tính toán vượt trội so với máy tính truyền thống và các máy chủ, đặc biệt trong việc giải quyết các bài toán phức tạp và khó khăn.
Mô hình hệ thống tính toán hiệu năng cao bao gồm các máy tính kết nối qua internet hoặc một siêu máy tính, được thiết kế để giải quyết các bài toán phức tạp với tốc độ nhanh chóng.
− Tính toán hiệu năng cao thường được sử dụng trong một số vấn đề lớn của thế giới về khoa học, kĩ thuật, tài chính, môi trường …
Cách hoạt động của tính toán hiệu năng cao
Hệ thống tính toán hiệu năng cao bao gồm các nút máy tính đơn lẻ kết nối với nhau trong một cụm, cho phép thực hiện khối lượng lớn tính toán nhanh chóng.
− Các máy tính (nodes) được kết nối với nhau qua mạng Internet để trao đổi dữ liệu.
Vì sao HPC quan trọng:
− Tính toán hiệu năng cao (HPC) là một phần quan trọng trong hoạt động nghiên cứu và đổi mới công nghiệp nhiều thập kỉ qua
HPC hỗ trợ kỹ sư, nhà khoa học dữ liệu, nhà thiết kế và nhà nghiên cứu trong việc giải quyết các vấn đề lớn và phức tạp một cách nhanh chóng và tiết kiệm chi phí hơn so với phương pháp điện toán truyền thống.
Các lợi ích to lớn của HPC:
Hệ thống HPC (High-Performance Computing) mang lại hiệu suất vượt trội so với PC trung bình, giúp các tổ chức hoàn thành nhiệm vụ hiệu quả hơn và khai thác giá trị từ dữ liệu sẵn có Nhờ vào khả năng chạy các mô phỏng trên HPC trong quá trình phát triển, các sản phẩm và quy trình mới có cơ hội thành công cao hơn đáng kể, từ đó tạo lợi thế cạnh tranh rõ rệt.
Hệ thống HPC (High-Performance Computing) có tốc độ vượt trội, cho phép thực hiện các phép tính nhanh chóng với khả năng đo lường lên đến petaFLOPS hoặc exaFLOPS, trong khi máy tính thông thường chỉ đạt tốc độ gigaFLOPS hoặc teraFLOPS Điều này mang lại lợi thế lớn khi hoàn thành các phép toán trong vài phút hoặc vài giờ, thay vì mất cả ngày hoặc tháng.
Chi phí sử dụng HPC dựa trên đám mây giúp tiết kiệm thời gian và tiền bạc, mang lại lợi ích cho cả doanh nghiệp nhỏ và công ty khởi nghiệp Các doanh nghiệp chỉ cần chi trả cho lượng tài nguyên mà họ sử dụng, giúp tối ưu hóa ngân sách và nâng cao hiệu quả hoạt động.
Cải tiến HPC đóng vai trò quan trọng trong việc thúc đẩy đổi mới trên hầu hết mọi lĩnh vực, tạo động lực cho những khám phá khoa học đột phá Những tiến bộ này không chỉ nâng cao chất lượng cuộc sống mà còn mang lại lợi ích cho cộng đồng toàn cầu.
Tính toán song song là gì?
Mô hình tính toán song song
Mô hình tính toán song song hiện nay có 4 cách phân loại theo Flynn:
SISD: Single Instruction stream Single Data stream
● Một máy tính nối tiếp (không song song)
● Chỉ lệnh đơn: Chỉ có một luồng lệnh đang được CPU thực hiện trong bất kỳ một chu kỳ xung nhịp nào
● Dữ liệu đơn: Chỉ một luồng dữ liệu được sử dụng làm đầu vào trong bất kỳ một chu kỳ đồng hồ nào
● Đây là loại máy tính lâu đời nhất
● Ví dụ: máy tính lớn thế hệ cũ, máy tính mini, máy trạm và PC lõi đơn/bộ xử lý.
SIMD: Single Instruction stream Multiple Data stream
● Một loại máy tính song song
● Lệnh đơn: Tất cả các đơn vị xử lý thực hiện cùng một lệnh tại bất kỳ chu kỳ xung nhịp nhất định nào
● Nhiều dữ liệu: Mỗi đơn vị xử lý có thể hoạt động trên một phần tử dữ liệu khác nhau
● Thích hợp nhất cho các vấn đề chuyên biệt được đặc trưng bởi mức độ đều đặn cao, chẳng hạn như xử lý đồ họa/hình ảnh.
● Thực thi đồng bộ (lockstep) và xác định
● Hai loại: Mảng bộ xử lý và Đường ống Vector
MISD: Multiple Instruction stream Single Data stream
● Một loại máy tính song song
● Nhiều lệnh: Mỗi đơn vị xử lý hoạt động trên dữ liệu một cách độc lập thông qua các luồng lệnh riêng biệt.
● Dữ liệu đơn: Một luồng dữ liệu được đưa vào nhiều đơn vị xử lý.
● Rất ít (nếu có) ví dụ thực tế về loại máy tính song song này đã từng tồn tại.
Một số cách sử dụng có thể hiểu được bao gồm việc áp dụng nhiều bộ lọc tần số cho một luồng tín hiệu và việc sử dụng nhiều thuật toán mã hóa nhằm bẻ khóa một tin nhắn được mã hóa.
MIMD: Multiple Instruction stream Multiple Data stream
● Một loại máy tính song song
● Nhiều lệnh: Mỗi bộ xử lý có thể đang thực thi một luồng lệnh khác nhau
● Nhiều dữ liệu: Mọi bộ xử lý có thể đang làm việc với một luồng dữ liệu khác
● Việc thực thi có thể đồng bộ hoặc không đồng bộ, xác định hoặc không xác định
● Hiện tại, loại máy tính song song phổ biến nhất - hầu hết các siêu máy tính hiện đại đều thuộc loại này.
Nguyên lý thiết kế thuật toán song song
Để thực hiện xử lý song song, cần xem xét cả kiến trúc máy tính và các thuật toán song song Việc thiết kế các thuật toán song song yêu cầu phân tích và tối ưu hóa quy trình xử lý để đạt hiệu suất cao nhất.
− Phân chia dữ liệu cho các tác vụ.
− Chỉ ra cách truy cập và chia sẻ dữ liệu.
− Phân các tác vụ cho các tiến trình (bộ xử lí).
− Các tiến trình được đồng bộ ra sao
Khi thiết kế một thuật toán song song có thể sử dụng năm nguyên lí chính trong thiết kế thuật toán song song:
Nguyên lý lập lịch nhằm mục đích tối ưu hóa việc sử dụng bộ xử lý trong thuật toán, đảm bảo rằng thời gian tính toán không bị tăng lên, đồng thời duy trì độ phức tạp ở mức thấp nhất.
Nguyên lý hình ống là một phương pháp hữu ích trong các bài toán có chuỗi thao tác liên tiếp {T1, T2, , Tn}, trong đó mỗi thao tác Ti+1 chỉ được thực hiện sau khi thao tác Ti hoàn thành.
Nguyên lý chia để trị cho phép chia nhỏ bài toán thành các phần độc lập và giải quyết chúng song song Đồng thời, nguyên lý đồ thị phụ thuộc dữ liệu giúp phân tích mối quan hệ giữa các dữ liệu để tạo ra đồ thị phụ thuộc, từ đó xây dựng các thuật toán song song hiệu quả.
Nguyên lý điều kiện tương tranh quy định rằng khi hai tiến trình cùng muốn truy cập vào một mục dữ liệu chia sẻ, chúng cần phải tương tranh với nhau Điều này có nghĩa là sự truy cập của chúng có thể cản trở lẫn nhau, dẫn đến những xung đột trong quá trình xử lý dữ liệu.
Giới thiệu thư viện MPI
MPI, hay Giao diện Truyền Thông Tin, là một thư viện hàm được sử dụng trong C/C++ và Fortran, cho phép lập trình viên tích hợp vào mã nguồn để thực hiện việc trao đổi dữ liệu giữa các tiến trình hiệu quả.
MPI thường sử dụng cho hệ thống có kiến trúc bộ nhớ phân tán (hệ thống máy tính phân cụm (cluster))), tuy nhiên nó cũng hoạt động bình
Chương trình MPI được dịch và chạy trên nền tảng có hỗ trợ chuẩn MPI
Trong lập trình MPI, các tiến trình giao tiếp thông qua việc gọi các hàm thư viện để gửi và nhận thông điệp từ các tiến trình khác Thông thường, số lượng bộ xử lý được xác định khi khởi tạo chương trình, và mỗi tiến trình được tạo ra trên một bộ xử lý riêng biệt Tuy nhiên, các tiến trình này có khả năng chạy các chương trình khác nhau.
Một chương trình MPI bao gồm nhiều chương trình tuần tự có trao đổi dữ liệu với nhau thông qua việc gọi các hàm trong thư viện.
Cấu trúc của chương trình MPI:
#include "stdio.h" int main(int argc, char* argv[])
{ //Khoi tao moi truong cho MPI
MPI_Init(&argc, &argv); int numtasks;
//Chua so process trong group int idtask;
//Chua gia tri id cua moi process MPI_Comm_size(MPI_COMM_WORLD, &numtasks);
MPI_Comm_rank(MPI_COMM_WORLD, &idtask);
//in ra id cua cac tac vu va so tac vu khoi tao printf("Id tac vu: %d trong tong so: %d\n", idtask, numtasks);
//Giai phong moi truong MPI_Finalize(); return 0;
Một nhóm trong MPI là tập hợp có thứ tự của các tiến trình, mỗi tiến trình được gán một ID duy nhất gọi là rank, với giá trị từ 0 đến N-1, trong đó N là tổng số tiến trình Trong hệ thống, nhóm được biểu diễn như một đối tượng và chỉ có thể được truy cập thông qua "handle" Mỗi nhóm luôn liên kết với một đối tượng communicator.
Một communicator là một nhóm các tiến trình có khả năng giao tiếp với nhau Tất cả các thông điệp MPI phải được chỉ định bởi một communicator Tương tự như nhóm, các communicator được lưu trữ trong bộ nhớ dưới dạng các đối tượng và lập trình viên chỉ có thể truy cập chúng thông qua các phương thức đã định.
“handle” Ví dụ, handle cho một communicator là bao gồm tất cả các tác vụ của MPI_COMM_WORLD.
Về khía cạnh lập trình viên, một nhóm và một communicator là một.
● Ban đầu MPI được thiết kế cho các kiến trúc bộ nhớ phân tán, kiến trúc rất phổ biến thời kỳ 1980 đến đầu năm 1990.
Xu hướng công nghệ hiện nay đang thay đổi mạnh mẽ, dẫn đến sự kết hợp giữa bộ nhớ chia sẻ và mạng máy tính Sự kết hợp này tạo ra một hệ thống lai, kết hợp ưu điểm của cả hai loại bộ nhớ: bộ nhớ chia sẻ và bộ nhớ phân tán.
● Thực thi MPI tương thích với cả hai kiểu kiến trúc trên và cũng tương thích với các kiểu kết nối và giao thức khác nhau.
● Ngày nay MPI có thể chạy trên hầu hết các nền tảng phần cứng:
● Dạng lai hai loại trên
Giới thiệu thuật toán Fox
Định nghĩa
Thuật toán Fox là một phương pháp phổ biến để nhân hai ma trận và nhiều ma trận lớn hơn Thuật toán này hoạt động theo cơ chế song song, cho phép chia ma trận thành các khối con và thực hiện tính toán đồng thời, giúp tăng tốc độ xử lý.
Nguyên lý hoạt động
Các phần tử n của hai ma trận A và B được phân chia giữa p bộ xử lý, với mỗi bộ xử lý lưu trữ một lượng dữ liệu là (√n) * (√n).
Thuật toán sẽ nhân một phần tử từ ma trận A với tất cả các phần tử trong cùng hàng của ma trận B Sau đó, nó sẽ dịch chuyển các khối lệnh của ma trận B lên một bước dọc theo các cột của bộ xử lý.
● Ban đầu chọn khối chéo Ai,i để phân vùng các bộ xử lý
1 Sau khi hoàn thành phần trên, ta lập lại thêm p lần:
2 Nhân Ai,i với tất cả các phần tử trong hàng của ma trận B
3 Nhân ma trận A vừa nhận được với ma trận B
4 Dịch khối B lên trên một bước
5 Sau đó chọn khối Ai,(j+1)mod √ ❑và nhân với các phần tử trong cùng
Phân tích độ phức tạp của thuật toán
Độ phức tạp tính toán của phép nhân ma trận phản ánh tốc độ thực hiện thao tác này trong các thuật toán đại số tuyến tính và tối ưu hóa Theo định nghĩa toán học, việc nhân hai ma trận n × n yêu cầu n³ thao tác, dẫn đến độ phức tạp O(n³) với thuật toán tuần tự Ngược lại, khi áp dụng thuật toán song song Fox, độ phức tạp giảm xuống còn O(n²s), trong đó s là số bước thực hiện Ví dụ, với phép nhân hai ma trận 4x4, ta có n = 4 và s = 4, dẫn đến độ phức tạp O(4² * 4) = O(64).
Mô tả, thực hiện và đánh giá hiệu năng của bài toán 20
Giới thiệu bài toán
Cho ma trận A = (aij)mxn
Nếu ma trận A có kích thước m x n và ma trận B có kích thước n x p, thì ma trận tích C = A * B sẽ có kích thước m x p Phần tử tại hàng thứ i và cột thứ j của ma trận C được xác định dựa trên quy tắc nhân ma trận.
Từ kiến thức trên ta áp dụng để tính ma trận theo tuần tự và theo thuật toán Fox song song.
Thực hiện thuật toán Fox
#define max 250 int main() { clock_t tstart, tend; float time_use; int a[max][max], b[max][max], c[max][max], i, j, k; int sum = 0;
// hàm clock là lấy thời gian thực hiện chương trình trước khi gọi hàm tstart = clock(); printf("\nThoi gian bat dau: %6.3f", tstart);
// khởi gắn giá trị ngẫu nhiên cho 2 ma trận A và B for (i = 0; i < max; i++) { for (j = 0; j < max; j++) { a[i][j] = rand() % (10 - 1); b[i][j] = rand() % (10 - 1);
} printf("Ma tran dau tien: \n"); for (i = 0; i < max; i++) { for (j = 0; j < max; j++) { printf(" %d ", a[i][j]);
} printf("Ma tran thu hai: \n"); for (i = 0; i < max; i++) { for (j = 0; j < max; j++) { printf(" %d ", b[i][j]);
//thực hiện tính toán nhân 2 ma trận for (i = 0; i p);
/* đặt kích thước ma trận vuông bằng q*/ dimensions[0] = dimensions[1] = grid->q;
/* đặt mảng wrap_around để tạo ranh giới quanh ma trận khi xử lý bài toán */ wrap_around[0] = 0; wrap_around[1] = 1;
/* MPI_Cart_create((MPI_COMM_WORLD)trình giao tiếp cũ, (2)kích thước cấu trúc,
(dimensions)kích thước mỗi chiều,
(wrap_around)số lượng chu kỳ, reoder = 1 cho phép MPI xác định quy trình tối ưu, &(grid -> comm)trình giao tiếp mới */
MPI_Cart_create(MPI_COMM_WORLD, 2, dimensions, wrap_around, 1, &(grid -> comm));
/* gắn lại xếp hạng sau khi sắp xếp trình tự thành đúng với reoder = 1 */
MPI_Comm_rank(grid->comm, &(grid->my_rank));
/* lấy tọa độ cho tiến trình hiện tại */
MPI_Cart_coords(grid->comm, grid->my_rank, 2, coordinates);
/* đặt lại tọa độ trong mảng coordinates sau khi sắp xếp thành đúng */ grid->my_row = coordinates[0]; grid->my_col = coordinates[1];
/* tạo cổng giao tiếp hàng */ free_coords[0] = 0; free_coords[1] = 1; /* từ đó hàng cũng thay đổi */
//Tạo lưới mới với số chiều free_coords từ trình giao tiếp cũ qua trình //giao tiếp mới của grid
//MPI_Cart_sub((grid->comm)trình giao tiếp cũ, (free_coords)số chiều của //ma trận,
&(grid->row_comm)trình giao tiếp mới)
MPI_Cart_sub(grid->comm, free_coords, &(grid->row_comm));
/* tạo cổng giao tiếp cột */ free_coords[0] = 1; free_coords[1] = 0; /* từ đó cột cũng thay đổi */
MPI_Cart_sub(grid->comm, free_coords, &(grid->col_comm));
/* Hàm thực hiện nhân 2 ma trận theo kiểu tuần tự */ void matmul(int** a, int** b, int** c, int size)
{ int i, j, k; int** temp = (int**)malloc(size * sizeof(int*)); for (i = 0; i < size; i++)
*(temp + i) = (int*)malloc(size * sizeof(int)); for (i = 0; i < size; i++)
{ temp[i][j] = 0; for (k = 0; k < size; k++) { temp[i][j] = temp[i][j] + (a[i][k] * b[k][j]);
} } } for (i = 0; i < size; i++) for (j = 0; j < size; j++) c[i][j] += temp[i][j];
// Hàm truyền dữ liệu từ vùng đệm đến ma trận a void transfer_data_from_buff(int* buff, int** a, int buffsize, int row, int col) { if (buffsize != row * col)
{ printf("transfer_data_from_buf: buffer size does not match matrix size!\n"); exit(1);
// Hàm truyền dữ liệu từ ma trận a đến bộ nhớ đệm void transfer_data_to_buff(int* buff, int** a, int buffsize, int row, int col) { if (buffsize != row * col)
{ printf("transfer_data_to_buf: buffer size does not match matrix size!"); exit(1);
{ for (j = 0; j < col; j++) { buff[count] = a[i][j]; count++;
// Hàm nhân 2 ma trận sử dụng thuật toán Fox void Fox(int n, GridInfo* grid, int** a, int** b, int** c)
The code snippet initializes a pointer to a pointer for a temporary matrix, along with a buffer for broadcasting and receiving data It defines variables for the stage of processing, the root node, the dimensions of submatrices based on matrix size and grid configuration, and indices for source and destination nodes.
MPI_Status status; submat_dim = n / grid->q;
/* khởi tạo bộ nhớ tempa */ tempa = (int**)malloc(submat_dim * sizeof(int*)); for (i = 0; i < submat_dim; i++)
*(tempa + i) = (int*)malloc(submat_dim * sizeof(int));
/* khởi tạo bộ nhớ bộ đệm */ buff = (int*)malloc(submat_dim * submat_dim * sizeof(int));
In this process, we shift the elements of matrix B up by one unit The source is calculated as (grid->my_row + 1) % grid->q, which determines the lowest element to be linked to the source The destination for the current element is set to (grid->my_row + grid->q - 1) % grid->q, effectively moving the current element up by one unit during each iteration of the loop, which runs for a total of grid->q stages.
{ root = (grid->my_col + stage) % grid->q;
// Nếu thứ hạng hiện tại bằng với số cột thì : if (root == grid->my_col) {
// lấy ma trận a gắn vào buff transfer_data_to_buff(buff, a, submat_dim * submat_dim, submat_dim, submat_dim);
// MPI_Bcast: phát 1 thông báo từ quy trình có thứ hạng //nhất định, ở đây là root tới tất cả
// các quy trình khác của bộ giao tiếp trong đó:
// MPI_Bcast(buff(địa chỉ bộ đệm), submat_dim * //submat_dim(số phần tử trong bộ đệm để truyền),
// MPI_INT(kiểu dữ liệu của bộ đệm int), root(thứ hạng của //gốc), grid-> row_comm(công giao tiếp)) row_comm);
Sau khi truyền thông tin, hãy gắn lại buff vào ma trận a thông qua quy trình transfer_data_from_buff(buff, a, submat_dim * submat_dim, submat_dim, submat_dim).
// Thực hiện nhân ma trận a với ma trận b theo kiểu tuần tự matmul(a, b, c, submat_dim);
} else { // Lấy ma trận tempa gắn vào buff transfer_data_to_buff(buff, tempa, submat_dim * submat_dim, submat_dim, submat_dim);
// Tiếp tục phát thông báo từ quy trình có thứ hạng root //đến tất cả các quy trình khác của bộ giao tiếp
MPI_Bcast(buff, submat_dim * submat_dim, MPI_INT, root, grid -> row_comm);
Sau khi truyền thông tin đến các quy trình khác, cần lấy buff và gắn lại vào ma trận tempa Để thực hiện điều này, sử dụng hàm transfer_data_from_buff(buff, tempa, submat_dim * submat_dim, submat_dim, submat_dim).
// Nhân 2 ma trận tempa và b theo kiểu tuần tự matmul(tempa, b, c, submat_dim);
// lấy ma trận b gắn vào buff transfer_data_to_buff(buff, b, submat_dim * submat_dim, submat_dim, submat_dim);
/* Hàm thực hiện gửi và nhận bằng một bộ đệm duy nhất, đồng thời thực hiện dịch cột b lên
The MPI_Sendrecv_replace function is used for simultaneous sending and receiving of data in parallel computing It requires several parameters: the buffer containing the memory address for both sending and receiving, the total number of elements specified by submat_dim * submat_dim, the data type as MPI_INT, the destination rank, the send tag set to 0, the source rank, the receive tag also set to 0, the communication port grid->col_comm, and a status variable to capture the returned status This function efficiently facilitates data exchange between processes in a distributed system.
MPI_Sendrecv_replace(buff, submat_dim * submat_dim, MPI_INT, dest, 0, source, 0, grid->col_comm, &status);
Sau khi gửi và nhận buff cho các bộ xử lý, cần gắn lại buff vào ma trận b Để thực hiện điều này, sử dụng hàm transfer_data_from_buff(buff, b, submat_dim * submat_dim, submat_dim, submat_dim).
// Hàm khởi tạo ma trận void initialiseAB()
// khởi tạo ma trận đầu vào A và B với giá trị bằng nhau for (i = 0; i < N; i++)
{ int i, j, dim; int** localA; int** localB; int** localC; double wtime; double w;
// Khởi tạo môi trường MPI
// Đặt wtime bằng thời gian xử lý những câu lệnh phía trên tại hàm main wtime = MPI_Wtime();
/* Khởi tạo ma trận A và B */ initialiseAB();
/* Tính kích thước ma trận cục bộ tại hàm main */ dim = N / grid.q;
Để cấp phát bộ nhớ cho hai ma trận A, B và ma trận kết quả C, ta sử dụng hàm `malloc` để cấp phát bộ nhớ cho các con trỏ ma trận Cụ thể, ta cấp phát `dim` con trỏ cho mỗi ma trận, với cú pháp `localA = (int**)malloc(dim * sizeof(int*));`, `localB = (int**)malloc(dim * sizeof(int*));` và `localC = (int**)malloc(dim * sizeof(int*));` Sau đó, ta sử dụng vòng lặp `for` để lặp qua các chỉ số từ 0 đến `dim - 1`.
*(localA + i) = (int*)malloc(dim * sizeof(int));
*(localB + i) = (int*)malloc(dim * sizeof(int));
*(localC + i) = (int*)malloc(dim * sizeof(int));
// Tại đây ta tính toán các ma trận cục bộ localA và localB
In this code snippet, all values of the local matrix C are initialized to zero The base row is calculated as the product of the grid's row index and the dimension, while the base column is determined similarly A loop iterates through the rows, setting each element of the matrix within the specified range to zero.
{ for (j = base_col; j < base_col + dim; j++) { localA[i - (base_row)][j - (base_col)] = matrixA[i][j]; localB[i - (base_row)][j - (base_col)] = matrixB[i][j]; localC[i - (base_row)][j - (base_col)] = 0;
// Sử dụng thuật toán Fox để tính tích 2 ma trận
Fox(N, &grid, localA, localB, localC);
// In kết quả ra màn hình printf("rank=%d, row=%d col=%d\n", grid.my_rank, grid.my_row, grid.my_col); for (i = 0; i < dim; i++)
{ for (j = 0; j < dim; j++) { printf("C[%d][%d]=%d ", i,j,localC[i][j]); printf("%d ", localC[i][j]);
// Gắn lại wtime bằng thời gian xử lý những câu lệnh phía trên trừ đi //thời gian bắt đầu wtime = MPI_Wtime() - wtime; printf("\nChuong trinh can %6.3f giay\n", wtime);
// Hàm kết thúc môi trường MPI
Để chạy chương trình với thuật toán song song, mở Command Prompt và điều hướng đến file exe trong thư mục x64/Debug Sau đó, sử dụng lệnh mpiexec -n 16 [tên-file].exe, trong đó 16 là số lượng bộ xử lý tham gia, có thể điều chỉnh theo số bộ xử lý có trên máy tính.
Ta có kết quả sau:
Đánh giá hiệu năng
Tất cả kết quả trên đều chạy trên máy có hệ điều hành WIN 11 - 64 bit với Intel® Core™ i9-12900H (20CPUs) ~2.9GHz, RAM 16GB
2.3.1 Đánh giá theo lý thuyết
Theo lý thuyết, xử lý bài toán theo kiểu song song sẽ giảm thời gian so với xử lý tuần tự Để chứng minh điều này, cần tính toán thời gian thực hiện cho cả hai phương pháp Sau khi có kết quả thời gian thực hiện tuần tự và song song, ta có thể tính hệ số speedup Từ hệ số speedup, hiệu quả của bài toán được xác định bằng cách chia speedup cho số bộ xử lý tham gia.
Hệ số tăng tốc (speedup) của thuật toán song song được định nghĩa là tỷ lệ giữa thời gian thực hiện trong tình huống xấu nhất của thuật toán tuần tự tối ưu và thời gian thực hiện tương ứng của thuật toán song song Trong các trường hợp thông thường, có thể hình dung khái niệm này một cách tương đối dễ hiểu.
Hiệu quả (Efficiency) của thuật toán song song được tính bằng:
2.3.2 Đánh giá theo thực tế
Ta có bảng so sánh hiệu năng với số bộ xử lý tham gia là 16:
Lần Ttuầntự Tsongsong SpeedUp Hiệu Quả
Từ kết quả trên ta thấy so với lý thuyết, bài toán thực tế đã đúng khi hệ số hiệu quả < 1
Kết quả được đánh giá dựa trên ma trận kích cỡ 250x250 Mặc dù chúng em đã cố gắng tăng kích thước ma trận để có đánh giá chính xác hơn, nhưng với tính toán tuần tự, chương trình chỉ có thể xử lý ma trận kích cỡ 250x250 trước khi bị treo Ngược lại, với tính toán song song sử dụng MPI, kích cỡ ma trận có thể lên đến khoảng 700x700 mà vẫn cho ra kết quả nhanh chóng, cho thấy rõ sự ưu việt của tính toán song song so với tính toán tuần tự.
Tổng kết 30
Nội dung đã thực hiện
Sau một thời gian dài bắt tay vào nghiên cứu nhóm chúng em đã hoàn thành đề tài “ĐÁNH GIÁ HIỆU NĂNG THUẬT TOÁN TÍNH TÍCH MA TRẬN FOX
SỬ DỤNG MPI” Qua đây bản thân em cũng như các thành viên trong nhóm đã học hỏi được nhiều kinh nghiệm:
Chúng em đã học được cách hợp tác hiệu quả trong làm việc nhóm, từ đó tạo ra một môi trường làm việc tích cực và sáng tạo.
Tận dụng cơ hội để nghiên cứu sâu về tính toán hiệu năng cao và tính toán song song, chúng tôi đã hiểu rõ hơn về thuật toán Fox Qua đó, chúng tôi có thể đánh giá hệ số tăng tốc (speedup) và hiệu quả của bài toán trong môi trường tính toán song song.
− Mỗi thành viên trong nhóm đã trải qua sự tìm hiểu, nghiên cứu từ đó giúp nâng cao kỹ năng chuyên môn cá nhân hơn.
Nhóm chúng em đã hoàn thành bài tập lớn này dựa trên những kiến thức đã thu nhận được Tuy nhiên, chúng em nhận thức rằng báo cáo vẫn còn nhiều hạn chế và mong thầy thông cảm cho những thiếu sót này.
Hướng phát triển
Trong quá trình thực hiện bài tập lớn, các thành viên trong nhóm đã cải thiện đáng kể kỹ năng chuyên môn của mình Điều này cho thấy nhóm phát triển không chỉ dừng lại ở bài tập lớn mà còn có khả năng mở rộng và phát triển hơn nữa trong tương lai.
Tạo đồ án từ bài tập lớn này nhằm xây dựng một sản phẩm chất lượng cao hơn, với sự ứng dụng rộng rãi của tính toán hiệu năng cao.
Nhóm phát triển sẽ xem xét khả năng tham gia và quản lý các dự án thực tế nếu các thành viên đáp ứng đủ kỹ năng và kiến thức, từ đó mang lại giá trị thực cho cộng đồng và doanh nghiệp.