Bài toán N-body quan tâm tới các lực giữa các hạt/vật thể trong không gian. Mỗi một cặp các hạt này sinh ra một lực nào đó, vì vậy ta giả sử có N hạt thì mỗi một hạt sẽ chịu tác động của N−1lực khác nhau, N−1lực này tổng hợp lại tạo thành một lực tổng hợp. Hợp lực này làm tăng tốc của hạt.
Bài toán N-body có thể được định nghĩa như sau: Trong một hệ thống gồm nhiều vật thể, giả sử ta đã biết trạng thái ban đầu của tất cả các vật thể trong hệ thống, ví dụ như vận tốc và vị trí ban đầu. Ta phải đánh giá tất cả các lực tương tác giữa các vật thể để nhận được vận tốc mới và vị trí mới của các vật thể trong hệ thống. Vì vậy, bằng việc thực hiện lặp lại các đánh giá này chúng ta có được thông tin về sự biến đổi theo thời gian của hệ thống.
Bài toánN-body xuất hiện trong việc nghiên cứu lý thuyết cấu trúc quy mô lớn như vũ trụ, các thiên hà, các chùm sao, hệ mặt trời, các động lực học tinh tế, mô hình khí hậu, khí đốt, chất lỏng và plasma; trong các quy mô nhỏ hơn như các hiệu ứng lượng tử. Bài toán N-body cũng được ứng dụng trong việc mô phỏng các phân tử sinh vật lớn như sự mở ra của protein hiện thực và axit deoxyribonucleic(DNA). Thuật toán cơ bản cho bài toánN-body như sau:
t =0
whilet <tfinal
fori=1to N ... N =số các hạt tính f(i)=lực tác dụng lên hạt i
sự di chuyển của hạt i dưới tác dụng của f(i) trong thời gian dt end for
tính các đặc trưng cần quan tâm của các hạt
t =t+dt ... dt=thời gian gia tăng end while
Thuật toán yêu cầuN2 phép tính. Giả sử chúng ta cần xác định vị trí của các vật thể trong vũ trụ, giả thiết có 1011 vật thể, nó cần lặp lại1022 phép tính. Nếu máy tính thời gian xử lý một phép toán của máy tính tuần tự là 1µsthì mất khoảng109 năm để lặp lại một quá trình tính toán.
Để giảm số lượng các hạt trong tổng lực, chúng ta có thể sử dụng quan sát sau: một cụm các hạt ở xa có thể được xấp xỉ bằng một điểm đơn có khối lượng bằng tổng của tất cả các hạt trong cụm và có tâm đặt tại tâm của cụm. Xét một thuật toán tuần tự dựa trên quan sát trên đó là, thuật toán Barnes-Hut.
Hình 2.14:Xấp xỉ cụm các hạt bằng một điểm đơn
Thuật toán Barnes-Hut
Thuật toán Barnes-Hut là lời giải phân cấp đơn giản nhất cho bài toán N- body, được ứng dụng nhiều trong vật lý học thiên thể(astrophysic). Thuật toán được J.Barnes và P.Hut đưa ra lần đầu tiên vào năm 1986 dựa trên thuật toán do A.Apel đưa ra năm 1985. Thuật toán sử dụng phép xấp xỉ cụm với dạng một cách chia để trị khéo.
Thuật toán coi toàn bộ không gian là một hình lập phương có chứa tất cả các vật thể. Đầu tiên, hình lập phương này được chia thành 8 hình lập phương con, nếu hình lập phương con không chứa hạt sẽ bị xóa từ cha tương ứng. Nếu hình lập phương chứa nhiều hơn một hạt sẽ được chia tiếp tục cho tới khi mỗi hình lập phương chỉ chứa một hạt. Cách chia này tạo thành octtree, các lá là các hình lập phương chứa một hạt.
Đối với bài toán thuộc không gian hai chiều, ta sẽ chia không gian thành 4 phần, mỗi phần con được chia liên tục cho tới khi mỗi vùng chỉ chứa một vật thể, tạo thành quadtree.
Sau khi cây được xây dựng, tổng khối lượng và tâm của số các vật thể chứa trong hình lập phương con được lưu tại node đó. Lực tác dụng lên vật thể có thể tính được bằng cách duyệt cây bắt đầu tại gốc, và dừng tại node mà tại đó phép xấp xỉ có thể sử dụng cho một vật thể cụ thể, trong trường hợp còn lại thì tiếp tục duyệt cây theo chiều từ trên xuống.
Trong bài toán N-body lớn, một tiêu chuẩn được sử dụng để phép xấp xỉ có thể được thực hiện là: Giả sử cụm các vật thể nằm trong một khối lập phương có thể tích là d×d×d, khoảng cách tới tâm của cụm là r. Ta có thể sử dụng phép xấp xỉ khi
r≥ d
trong đó,θ là hằng số không đổi,θ ≤1.
Tổng khối lượngT M tại một node là tổng của khối tại các node con,
T M=
k
∑∑∑
i=0
mi
trong đó, mi là tổng khối lượng tại node con thứ i. Tâm khối, TC, được tính bằng công thức TC = 1 T M k ∑ ∑∑ i=0 (mi×ci)
trong đó,ci là tâm khối của con thứi.k=4nếu sử dụng cây quadtree,k=7nếu sử dụng cây octtree.
Khi tất cả các vật thể có vận tốc và vị trí mới, thì quá trình tính toán được lặp lại với mỗi khoảng thời gian.
Thuật toán tuần tự Barnes-Hut có thể được miêu tả như sau như sau:
1. Xây dựng cây quadtree/octtree gồm các thủ tuc: dựng cây, và chèn thêm node vào cây đã trình bày ở phần trên.
2. Tính khối lượngT M và tâm của khốiCMtại mỗi node.
( TM, CM )=Compute_Mass( root )
function ( TM, CM )=Compute_Mass( n ) . . . Tính CM và TM của node n if n chứa 1 hạt
. . . TM and CM bằng khối lượng và vị trí của hạt lưu (TM, CM) tại node n
return (TM, CM)
else . . . “post order traversal”: tính toán tại tất cả node con trước for tất cả node con c(j) of n . . . j=1,2,3,4
( TM(j), CM(j) )=Compute_Mass( c(j) ) endfor
T M=T M(1) +T M(2) +T M(3) +T M(4)
CM= (T M(1)∗CM(1)+T M(2)∗CM(2)+T M(3)∗CM(3)+T M(4)∗
CM(4))/T M
lưu ( TM, CM ) tại node n return ( TM, CM )
end if
3. Tại mỗi node ta cần duyệt lại cây để tính tổng lực tác động lên nó.
for k=1 to N
f(k)=TreeForce( k, root )
. . . tính lực tác dụng lên hạt k do tất cả các hạt tại nút gốc gây ra endfor
function f =TreeForce( k, n ) . . . tính lực tác dụng lên node k của tất cả các hạt chứa trong node n
f=0
if n chứa 1 tính f theo công thức trực tiếp else
r=khoảng cách từ k tơi tâm của các hạt trong node n d=cỡ của node n
ifd/r<θ . . . sử dụng phép xấp xỉ cụm tính f sử dụng công thức xấp xỉ else . . . cần xem xét bên trong node
for mỗi node con c của node n
f = f +TreeForce(k,c)
end for end if end if
Độ phức tạp thời gian của thuật toán Barnes-Hut là O(nlogn)[1]
Đây là một ví dụ ứng dụng thành công thuật toán Barnes-Hut trong bài toán nghiên cứu sự hình thành và phân bố thống kê của các thiên hà trong vật lý học thiên thể. Các mô phỏng bào gồm 17 triệu ngôi sao trong hơn 600 nhịp thời gian. Thuật toán thực hiện hết 24 giờ trên một máy Intel Delta 512 bộ xử lý, tốc độ tính toán của mỗi bộ xử lý là 10 Mflops, tốc độ tính toán của máy là khoảng trên 5 Gflops. Trong khi đó nếu sử dụng thuật toán tuần tự O(n2)trên máy tính như trên thì thời gian thực hiện là khoảng một năm[1].
Song song hóa thuật toán Barnes-Hut
Thuật toán Barnes-Hut có thể được song song tốt trên cả hai loại máy tính chia sẻ và phân tán bộ nhớ. Ta sử dụng hai chiến lược được gọi là phân vùng không gian(spatial partitioning) và phân vùng cây(tree partitioning).
Spatital partitioning Phương pháp này chia không gian chứa các hạt thành các hình chữ nhật(trong không gian 2-chiều) không gối lên nhau với số hạt trong mỗi phần bằng (hoặc xấp xỉ) nhau, và gán mỗi phần cho một bộ xử lý/tiến trình, làm việc tốt khi là không gian 2-chiều. Phương pháp này được gọi là phương pháp chia đôi đệ quy trực giao(Orthogonal Recursive Bisection, ORB).
Đầu tiên, một đường thẳng đứng sẽ chia không gian thành hai phần với số hạt bằng nhau. Số các hạt trong mỗi phần được chia thành 2 phần bởi 2 đường nằm ngang. Quá trình này tiếp tục cho tới khi số các vùng bằng số các bộ xử lý được sử dụng.
Để bộ xử lýk chạy thuật toán Barnes-Hut trên các hạt được lưu ở bộ xử lý, bộ xử lý k cần truy cập vào một cây con của Quadtree để tính các lực tác dụng lên mỗi
hạt, cây này được gọi là cây địa phương quan trọng(Locally Essential Tree, LET). LET bao gồm toàn bộ cây con của quadtree mà các node lá của nó chứa trong bộ xử lý k.
Cây LET được xác định đơn giản như sau: Gọi n là node không chứa hạt nào trong k,D(n)là kích cỡ hình vuông tương ứng với noden,r(n)là khoảng cách ngắn nhất từ bất kì điểm nào trong n tới bất kì điểm nào trong k. Nếu hoặc
• (D(n)/r(n)<θ và(D(parent(n)))/r(parent(n))≥θ: tức là,T MvàCMcủa node n có thể được dùng để tính lực tác động vào bất kì hạt nào trong k, nhưng node cha của n thì không.
• D(n)≥r(n): ta cần duyệt node cha của n.
thì n là một phần của LET của tiến trình k. Giả sử n được lưu trong bộ xử lý j thì bộ xử lý j cần gửi n cho k.
Giả sử p=2m là số bộ xử lý sử dụng. Mỗi bit thứ m của bộ xử lý xác định một lần chia đôi trực giao, bit bằng 0 nếu phần chia gán cho bộ xử lý nằm bở bên trái hoặc bên trên của đường chia đôi trực giao, bằng 1 nếu ngược lại.
Mã giả được thực hiện cho mỗi tiến trình như sau:
tính quadtree cục bộ T LET=T
for i=0to m-1 ... p=2m
OTHERPROC=MYPROC xor 2i ... đẩy bít thứ i của MYPROC
tính các tập con của LET mà các bộ xử lý trên nửa còn lại của đường chia i yêu cầu Gửi tập con này tới OTHERPROC
Nhận tập con tương ứng từ OTHERPROC, và nhập vào cây LET end for
Gỡ LET của các phần không cần thiết
Tree partitioning Cách tiếp cận này được goi làcostzonestrong hệ thống bộ nhớ chia sẻ,hashed octtreetrong hệ thống bộ nhớ phân tán. Thuật toán làm việc tốt trên không gian 3-chiều. Ta sẽ thay việc phân nhỏ cây bằng việc phân nhỏ cây QuadTree. Thuật toán cho bộ nhớ chia sẻ:
• Ước lượng công việc cần thực hiện để tính lực tại các node lá. Nếu là lần xây dựng cây đầu tiên thì ước lượng theo chiều sâu của node trong cây, nếu ngược lại ta ước lượng từ lời gọi cuối cùng tới Barnes-Hut.
• Phân tổng công việc trong các lá vào p khối(costzones), lượng công việc ở mỗi khối làW/p. Khối có thể chứa các hình vuông không kề nhau.
Hình 2.16: Dùng các khối để hiển thị công việc của QuadTree vào 4 bộ xử lý, khối công việc được khoanh vòng.
Thuật toán cho bộ nhớ phân tán: Thuật toán sử dụng một cấu trúc dữ liệu phân tán được gọi là hashed octtree.
• Đầu tiên, ta kết hợp mỗi node của cây với một khóa (key)duy nhất trong cây quadtree. Xác định khóa như sau: (x,y)là tọa độ tâm của hình vuông, ta coi x, y là các xâu bit. Key được tạo bằng cách đặt xen kẽ các bít với nhau từ phải sang trái và thêm 1 vào đầu bên trái.
Hình 2.17:Xây dựng một key cho hashed quadtree
• Sau đó, sử dụng hàm băm hash(key) để gán mỗi khóa vào một địa chỉ toàn cục trong một bảng băm được lưu tại bộ nhớ toàn cục. Hàm bămhash(key)là một mặt nạ bit đơn giản của hbit cuối cùng của mỗi khóa. Do vậy, các mức
(h/2) +1của cây quadtree ((h/3) +1của cây octree) có một giá trị duy nhất trong hàm băm. Mỗi mục của bảng băm gồm một con trỏ trỏ tới danh sách các hộp có cùng giá trị trong hàm băm, một danh sách các ô có mặt, dữ liệu khối lượng và tâm của khối.
• Bảng băm được phân bố tới các bộ xử lý sử dụng kĩ thuật tương tự như cost- zones. Giả sử h=4sẽ có một ô trong bảng băm tương ứng với một ô trong 16 ô vuông tại mức 3 của cây quadtree, các khóa này sẽ xác định một thứ tự tuyến tính cho các ô. Ta cần chia 16 ô vuông này thành pkhối kề nhau để gán vào các bộ xử lý. Tương tự như costzones cũng có các vùng không kề nhau cùng được gán vào một bộ xử lý.
Hình 2.18:Gán các mục của bảng băm vào 4 bộ xử lý.
phần của bảng băm.
Việc song song thuật toán Barnes-Hut đã làm giảm thời gian thực hiện một cách đáng kể. Đây là các kết quả thực hiện phép tính trong vật lý học thiên thể sử dụng thuật toán song song Barnes-Hut trên mát tính Intel Delta như trên. Một lần thực hiện thuật toán Barnes-Hut, giả sử các hạt có phân bố đểu, tổng thời gian thực hiện là 114 giây. Khi các vật thể có phân bố không đều, thời gian chạt là 160 giây.[1]
Chương 3
Chương trình ví dụ
Chương trình minh họa thuật toán bucket sort song song
1. Thực hiện song song thuật toán bucket sort bằng cách gán cho mỗi tiến trình một giỏ.
#include<stdio.h>
#include<mpi.h>
void sort(int ar[], int size) { int temp; int i,j; for (i=size−1; i>=1; i−−) for (j=0; j<i; j++) if (ar[j]>ar[j+1]) { temp=ar[j]; ar[j]=ar[j+1]; ar[j+1]=temp; } }
int main(int argc, char ∗argv[]) {
int rank,p; int a,b; int n;
int∗counters,∗displs; int start, end, slice; int count,disp,i;
MPI_Status status; MPI_Init(&argc,&argv);
MPI_Comm_size(MPI_COMM_WORLD,&p); MPI_Comm_rank(MPI_COMM_WORLD,&rank); // initialize random number generation
srand(time(NULL)); if (rank==0)
{
printf("How big should the array be ? "); scanf("%d",&n);
array=(int∗)malloc(sizeof(int)∗n);
printf("What is the value of a (size of the range): "); scanf("%d",&a);
// fill the array with random numbers for (i=0; i<n; i++) { array[i]=rand() % a; printf(" %d ", array[i]); } printf("\n"); }
// send array, a, and n
MPI_Bcast(&n,1,MPI_INT, 0, MPI_COMM_WORLD); MPI_Bcast(&a,1,MPI_INT, 0, MPI_COMM_WORLD); if (rank !=0)
array=(int∗)malloc(sizeof(int)∗n);
MPI_Bcast(array, n, MPI_INT, 0, MPI_COMM_WORLD); slice=a / p;
start=rank∗ slice;
end=(rank==p−1) ? a : start+slice; count= 0;
local=(int∗)malloc(sizeof(int)∗n); // create the local bucket
for (i=0; i<n; i++)
if (array[i] < end&&array[i]>=start) local[count++]=array[i]; // local sort the bucket
sort(local,count);
// Need to compute the displacements
MPI_Scan(&count,&disp,1,MPI_INT,MPI_SUM,MPI_COMM_WORLD); // Let master Gather the counts and the displacements
if (rank==0)
counters=(int∗)malloc(sizeof(int)∗p);
MPI_Gather(&count,1,MPI_INT, counters, 1, MPI_INT, 0, MPI_COMM_WORLD); // Let master Gather the displacements
if (rank==0) {
displs=(int∗)malloc(sizeof(int)∗(p+1)); displs[0]=0;
}
MPI_Gather(&disp,1, MPI_INT, displs+1, 1, MPI_INT, 0, MPI_COMM_WORLD); // gather the sorted buckets
MPI_Gatherv(local, count, MPI_INT, array, counters, displs, MPI_INT, 0, MPI_COMM_WORLD); if (rank==0) { printf("Sorted: "); for (i=0; i<n ; i++) printf(" %d ", array[i]); printf("\n"); } MPI_Finalize(); }
2. Thực hiện song song thuật toán bucket sort bằng cách chia nhỏ dãy thànhm miền con, mỗi miền ứng với một bộ xử lý và mỗi bộ xử lý chứa các giỏ nhỏ hơn.
Chương trình bao gồm 6 bước thực hiện như sau:
• Sinh mảng A ngẫu nhiên
• Phân phối A tới mỗi node
• Sinh các giỏ con tại mỗi nodes
• Sắp xếp tại mỗi giỏ trên mỗi nodes
• Phân phối các giỏ nhỏ tới các node phù hợp • Trộn kết quả tại node gốc
#include<stdio.h>
#include<mpi.h>
#define root 0
#define MCW MPI_COMM_WORLD #define NODES 4
#define MATSIZE NODES∗10
int main(int argc, char ∗argv[]) {
int rank, size, x, y, z, tmp; int mat1[MATSIZE]= {0}; int bigbucket[MATSIZE]={0};
int bucket[NODES][MATSIZE/NODES]={{0}}; int mat2[MATSIZE∗NODES] ={0};
int part[MATSIZE/NODES]= {0}; MPI_Init(&argc,&argv); MPI_Comm_rank(MCW,&rank); MPI_Comm_size(MCW,&size); if (size !=NODES) {
printf("Please only use 4 nodes or else modify NODES parameter\n"); MPI_Abort(MCW, -1);
}
if (rank==root) {
for (x=0; x<MATSIZE ; x++) //populating matrix with ran- dom values
{
mat1[x]=(rand() % (MATSIZE−1))+1 ; }
printf("Printing array\n");
for (x=0; x<MATSIZE ; x++) //printing {
printf("%d>%d\n", x, mat1[x]); }
}
//MPI_Barrier(MCW);
MPI_Scatter(&mat1, MATSIZE/NODES, MPI_INT, &part, MAT- SIZE/NODES, MPI_INT, root, MCW);
//Assign Values to relevant buckets
for (x=0; x<MATSIZE/NODES ; x++ ) {
bucket[part[x]/(MATSIZE/NODES)][x] = part[x]; } //Sorting Buckets internally
for (x=0; x<NODES; x++) { for (y=0; y<MATSIZE/NODES ; y++) { for (z =y+1; z<MATSIZE/NODES ; z++) { if (bucket[x][y] >bucket[x][z]) { tmp= bucket[x][y]; bucket[x][y]=bucket[x][z]; bucket[x][z]=tmp; } } } }
MPI_Alltoall(bucket, MATSIZE/NODES, MPI_INT, bigbucket, MAT- SIZE/NODES, MPI_INT, MCW);
printf("%d>Printing unsorted Big Buckets\n", rank); for (x=0; x<MATSIZE; x++)
{
printf("%d>Bucket[%d]=%d\n", rank, x, bigbucket[x]); }
//Sorting Big Buckets internally
for (y=0; y<MATSIZE ; y++) { for (z=y+1; z<MATSIZE; z++) { if (bigbucket[y]>bigbucket[z]) { tmp =bigbucket[y]; bigbucket[y]= bigbucket[z]; bigbucket[z]=tmp; } } }
printf("%d>Printing SORTED Big Buckets\n", rank); for (x=0; x<MATSIZE; x++)
{
printf("%d>Bucket[%d]=%d\n", rank, x, bigbucket[x]); }
MPI_Gather(&bigbucket, MATSIZE, MPI_INT,&mat2, MATSIZE, MPI_INT, root, MCW);
if (rank==root) {
printf("Printing Gathered Results\n");
for (x=0, y=0; x<MATSIZE*NODES ; x++) //printing { printf("%d>%d\n", x, mat2[x]); //trimming results if (mat2[x] !=0) { mat1[y++]=mat2[x]; } }
printf("Printing Sorted Gathered Results\n"); for (x=0; x<MATSIZE ; x++) { printf("%d>%d\n", x, mat1[x]); } } MPI_Finalize(); return 0; }
Tài liệu tham khảo