II.1 Thuật toán song song

Một phần của tài liệu Luận văn nghiên cứu các kiến trúc của máy tính song song, các mô hình và các thuật toán trong xử lý song song, (Trang 25 - 40)

II.1.1 Nguyên lý thiết kế thuật toán song song.

Như trên đã nêu, nói đến xử lý song song là phải xét cả kiến trúc máy tính lẫn các thuật toán song song.

Những thuật toán, trong đó có một số thao tác có thể thực hiện đồng thời được gọi là thuật toán song song. Tổng quát hơn, thuật toán song song là một tập các tiến trình hoặc các tác vụ có thể thực hiện đồng thời và có thể trao đổi dữ liệu với nhau để kết hợp cùng giải một bài toán đặt ra.

Thuật toán song song có thể xem như là một tập hợp các đơn thể độc lập, một số trong số chúng có thể thực hiện tương tranh trên máy tính song song.

Để thiết kế được các thuật toán song song cần phải trả lời các câu hỏi sau:

 Việc phân chia dữ liệu cho các tác vụ như thế nào?

 Dữ liệu được truy cập như thế nào, những dữ liệu nào cần phải chia sẻ?

 Phân các tác vụ cho các tiến trình (bộ xử lý) như thế nào?

 Các tiến trình được đồng bộ ra sao?

Có năm nguyên lý chính trong thiết kế thuật toán song song:

1. Các nguyên lý lập lịch: Tạo lịch trình để giảm tối thiểu các bộ xử lý sử dụng trong thuật toán sao cho thời gian tính toán là không tăng (xét theo khía cạnh độ phức tạp).

2. Nguyên lý hình ống: Nguyên lý này được áp dụng khi bài toán xuất hiện một dãy các thao tác {T1, T2, . . ., Tn}, trong đó Ti+1 thực hiện sau khi Ti kết thúc.

3. Nguyên lý chia để trị: Chia bài toán thành những phần nhỏ hơn tương đối độc lập với nhau và giải quyết chúng một cách song song.

4. Nguyên lý đồ thị phụ thuộc dữ liệu: Phân tích mối quan hệ dữ liệu trong tính toán để xây dựng đồ thị phụ thuộc dữ liệu và dựa vào đó để xây dựng thuật toán song song.

5. Nguyên lý điều kiện tương tranh : Nếu hai tiến trình cùng muốn truy cập vào cùng một mục dữ liệu chia sẻ thì chúng phải tương tranh với nhau, nghĩa là chúng có thể cản trở lẫn nhau.

Ngoài những nguyên lý nêu trên, khi thiết kế thuật toán song song còn một số điểm cần quan tâm.

 Tương tự như kiến trúc, hiệu quả thực hiện của thuật toán song song có thể rất khác nhau, mà yếu tố quan trọng nhất ảnh hưởng tới độ phức tạp tính toán là cấu hình tô pô liên kết mạng. Ví dụ: DAP là máy tính kiểu SIMD với 64 * 64 bộ xử lý, thời gian nhân ma trận là tuyến tính theo kích cỡ của ma trận và phụ thuộc vào đường truyền dữ liệu giữa các hàng với cột.

 Thuật toán song song phải được thiết kế dựa trên những kiến thức về kiến trúc máy tính, ngôn ngữ lập trình song song và các phương pháp tính toán.

II.1.2 Các cách tiếp cận trong thiết kế

Có ba cách tiếp cận để thiết kế thuật toán song song:

1. Thực hiện song song hoá những thuật toán tuần tự, biến đổi những cấu trúc tuần tự để tận dụng được những khả năng song song tự nhiên của tất cả các thành phần trong hệ thống xử lý.

2. Thiết kế những thuật toán song song mới phù hợp với kiến trúc song song. 3. Xây dựng những thuật toán song song từ những thuật toán song song đã được xây dựng cho phù hợp với cấu hình tôpô mạng và môi trường song song thực tế.

Như vậy, cách làm khá thông dụng là biến đổi các thuật toán tuần tự về song song, hay chuyển từ một dạng song song về dạng song song phù hợp hơn sao vẫn bảo toàn được tính tương đương trong tính toán. Do đó, khi biến đổi chúng ta cần trả lời hai câu hỏi:

1. Kiến trúc nào phù hợp cho bài toán?

2. Những bài toán loại nào sẽ xử lý hiệu quả trong kiến trúc song song cho trước?

Ví dụ: Những máy tính kiểu SIMD không thích hợp để giải các bài toán, trong đó có nhiều tiến trình dị bộ. Ngược lại, máy tính kiểu MIMD lại không hiệu quả để giải quyết những bài toán trong đó có nhiều tiến trình cần phải đồng bộ.

II.1.3Một số phương pháp chuyển đổi từ chương trình tuần tự về song song II.1.3.1 Sự phụ thuộc dữ liệu giữa các tiến trình trong chương trình

Như chúng ta đã biết, có rất nhiều phương pháp để chuyển đổi từ chương trình tuần tự sang chương trình song song. Thế nhưng không phải bất kỳ chương trình tuần tự nào chúng ta cũng có thể chuyển đổi sang chương trình song song một cách dễ dàng. Để thực hiện được các khối lệnh song song chúng ta phải hiểu rõ và xác định được tất cả các phụ thuộc dữ liệu của chúng trong chương trình, sau đó mô tả chúng thông qua đồ thị phụ thuộc dữ liệu.

Đồ thị phụ thuộc dữ liệu là một đồ thị định hướng G=(V,E), trong đó V là tập các lệnh trong chương trình, E là các phụ thuộc dữ liệu.

Ví dụ: Cho dãy lệnh S1, S2, S3 sau: S1: A := B + C

S2: B := A + E S3: A := A + B

Phân tích kỹ các câu lệnh trên chúng ta phát hiện ra một số sự phụ thuộc của chúng: 1. Lệnh S1 tính giá trị của biến A và biến này được sử dụng trong S2 và S3. Do vậy,

có sự phụ thuộc của S2, S3 vào S1 (ký hiệu là d1, d2).

2. Lệnh S2 tính giá trị của biến B và biến này được sử dụng trong S3. Do vậy, có sự phụ thuộc của S3 vào S2 (ký hiệu là d3).

3. Giá trị trước đó của biến B được sử dụng ở S1. Do vậy, có sự phụ thuộc vào S2 (ký hiệu d4 ). 4. Cả hai lệnh S1 và S3 cùng tính giá trị của biến A và do vậy, có sự phụ thuộc (ký hiệu d5). 5.Lệnh S3 tính giá trị của biến A và biến này được sử dụng trong S2 và S3. Do vậy, có sự phụ thuộc của S2, S3 vào S3 (ký hiệu là d6, d7).

Sự phụ thuộc dữ liệu giữa các câu lệnh S1, S2, S3 có thể được biểu diễn qua đồ thị phụ thuộc dữ liệu như sau:

Hình 2-1 Đồ thị phụ thuộc dữ liệu giữa S1, S2, S3.

Trong chương trình chúng ta chỉ xét những sự phụ thuộc giữa các câu lệnh đơn. Có 5 loại phụ thuộc về mặt dữ liệu trong chương trình:

Xét dãy lệnh gồm 2 câu lệnh S1, S2.

Gọi: - DEF(S1) - tập tất cả các biến có giá trị bị thay đổi khi thực hiện câu lệnh S1. - USE(S1) - tập tất cả các biến được truy cập (được sử dụng) khi thực hiện câu lệnh S1. - DEF(S2) - tập tất cả các biến có giá trị bị thay đổi khi thực hiện câu lệnh S2. - USE(S2) - tập tất cả các biến được truy cập (được sử dụng) khi thực hiện câu lệnh S2. a. Phụ thuộc dòng dữ liệu (Data Flow Dependency): là sự phụ thuộc dữ liệu giữa S1

và S2 khi DEF(S1) ∩ USE(S2) ≠ φ. Đây là loại phụ thuộc rất chung và rất khó loại bỏ bởi vì lệnh S2 yêu cầu giá trị của một biến và giá trị này phải được tính ở S1. Nghĩa là khi xuất hiện phụ thuộc dòng dữ liệu giữa các câu lệnh thì chúng không thực hiện song song được. Ví dụ: các phụ thuộc d1, d2, d3là loại phụ thuộc dòng dữ liệu.

Quan hệ phụ thuộc dòng dữ liệu được ký hiệu là:

b. Phản phụ thuộc dữ liệu (Data Anti-Dependency): là sự phụ thuộc dữ liệu giữa S1

và S2 khi DEF(S2) ∩ USE(S1) ≠ φ. Đây là loại phụ thuộc ngược với loại phụ thuộc nêu trên. Sự phụ thuộc này xuất hiện khi chúng ta sử dụng lại tên gọi của các biến, một biến đã được sử dụng trong S1 và sau đó được định nghĩa lại ở S2. Nghĩa là khi xuất hiện phản phụ thuộc dữ liệu giữa các câu lệnh thì chúng cũng không thực hiện song song được. Ví dụ: các phụ thuộc d4, d6, d7là loại phản phụ thuộc dữ liệu.

Quan hệ phản phụ thuộc dữ liệu được ký hiệu là:

d5 d3

S1 d1 S2 S3

d2

d4 d6

c. Phụ thuộc dữ liệu ra (Data Output Dependency): là sự phụ thuộc dữ liệu giữa S1

và S2 khi DEF(S2) ∩ DEF(S1) ≠ φ. Sự phụ thuộc này xuất hiện do hai nguyên nhân: thứ nhất sử dụng lại tên của các biến (dùng chung), thứ hai tính tăng giá trị của cùng một biến. Nếu những lệnh này thực hiện đồng thời thì chúng sẽ ghi đè các giá trị vào cùng một ô nhớ. Do vậy, cần phải xác định chính xác thứ tự thực hiện để ngăn ngừa việc sử dụng những giá trị không đúng. Ví dụ: các phụ thuộc d5là loại phụ thuộc dữ liệu ra.

Quan hệ phụ thuộc dữ liệu ra được ký hiệu là:

d. Phụ thuộc dữ liệu vào (Data Input Dependency): là sự phụ thuộc dữ liệu giữa S1

và S2 khi USE(S2) ∩ USE(S1) ≠ φ. Bởi vì các lệnh này chỉ truy cập (đọc) và không làm thay đổi giá trị của các biến đó, do vậy các lệnh này có thể thực hiện theo bất kỳ thứ tự nào cũng được, nghĩa là có thể thực hiện song song.

Quan hệ phụ thuộc dữ liệu vào được ký hiệu là:

e. Phụ thuộc điều khiển dữ liệu ( (Data Control Dependency): là sự phụ thuộc dữ liệu giữa S1 và S2 khi sự thực hiện của lệnh này phụ thuộc vào giá trị của các biến được tính ở lệnh kia.

Quan hệ phụ thuộc điều khiển dữ liệu được ký hiệu là:

II.1.3.2 Một số phương pháp để loại bỏ sự phụ thuộc dữ liệu giữa các tiến trình

Sự phụ thuộc giữa các câu lệnh đơn trong dãy lệnh có khả năng làm cho dãy lệnh không thể thực hiện song song được. Mặc dù vậy chúng ta có một số cách để loại bỏ sự phụ thuộc này.

- Loại bỏ sự phụ thuộc dữ liệu bằng cách đặt lại tên của các biến để tránh việc chia sẻ các biến và từ đó tăng được mức độ song song của chương trình.

Ví dụ: (S1):A = B + X (S2):C = A + D

Để loại bỏ sự phụ thuộc dữ liệu giữa (S1) và (S2), ta có thể thay biến A của câu lệnh (S2) bằng A1 như sau:

(S1): A = B + X (S2): C = A1 + D

- Loại bỏ sự phụ thuộc dữ liệu ra bằng cách sử dụng các biến khác nhau. Ví dụ: (S1): A = B + C + D

(S2): A = D * X

Để loại bỏ sự phụ thuộc dữ liệu giữa (S1) và (S2), ta có thể thay biến A của câu lệnh (S2) bằng A1 như sau:

(S1): A = B + C + D (S2): A1 = D * X

- Loại bỏ sự phản phụ thuộc dữ liệu bằng cách sử dụng các biến khác nhau hoặc thực hiện các phép biến đổi (phép thế).

Ví dụ 1:(S1): A = C + D (S2): C = D * X

Để loại bỏ sự phản phụ thuộc dữ liệu giữa (S1) và (S2), ta có thể sử dụng biến khác cho câu lệnh (S2) như sau:

(S1): A = C + D (S2): C1 = D * X

Ngoài ra ta có thể sử dụng một số cách biến đổi để loại bỏ các mối quan hệ về dữ liệu giữa các tiến trình.

Ví dụ 2: Xét dãy các câu lệnh sau:

(S1): A = B + C (S2): B = A * 3 (S3): A = 2 * C (S4): P = B >= 0 if (P is true) (S5): then D = 1 (S6): else D = 2 endif

Đồ thị phụ thuộc dữ liệu của đoạn chương trình trên được mô tả như hình vẽ:

S1 S2

Hình 2-2 Đồ thị phụ thuộc dữ liệu

Để xử lý được song song, thì cần thiết phải loại bỏ đi một số những loại phụ thuộc dữ liệu có thể. Ví dụ loại bỏ những quan hệ phản phụ thuộc dữ liệu và phụ thuộc dữ liệu kết quả bằng cách thay biểu thức tính B ở S1 vào S2, ta thu được đoạn chương trình sau: S2’: B = (B + C) * 3 S3: A = 2 * C S4: P = B >= 0 if (P is true) S5: then D = 1 S6: else D = 2 endif

Đồ thị phụ thuộc dữ liệu rút gọn của đoạn chương trình trên

Hình 2-3 Đồ thị phụ thuộc dữ liệu rút gọn S1 S3 S2 S4 S5 S6 S3 S2’ S4 S5 S6

Tuy nhiên, không phải lúc nào ta cũng có thể áp dụng thành công các phép biến đổi đơn giản đó.

Ví dụ, xét chu trình sau:

for (i = 0; i < N; i++) A = A + i;

Chu trình trên có sự phụ thuộc dữ liệu ra vì nó làm tăng giá trị của cùng một biến. Nếu thực hiện song song thì chúng ghi đè các giá trị vào cùng một ô nhớ. Điều này khiến chu trình cho kết quả sai khi thực hiện song.

Hiển nhiên có thể viết nó dưới dạng tương đương không sử dụng chu trình for

i = 0;

aa: if(i > N) stop; a = a + i;

i++; goto aa;

Trong mỗi bước lặp đều phải sử dụng những tên biến khác nhau nếu muốn loại bỏ phản phụ thuộc dữ liệu. Điều này không thể thực hiện được vì số vòng lặp là bất kỳ.

Chúng ta cũng cần lưu ý thêm là quan hệ phụ thuộc dữ liệu là không bắc cầu. Nghĩa là nếu S2 phụ thuộc vào S1, S3 phụ thuộc vào S2 thì không kết luận được S3 phụ thuộc dữ liệu vào S1.

Ngoài ra, chúng ta xét đến sự phụ thuộc theo chu trình và theo mảng:

II.1.3.3 Phụ thuộc theo chu trình và theo mảng

Trong thực tế, hầu hết các chương trình đều có các đoạn chương trình lặp theo một cấu trúc lặp nào đó. Chính vì thế nên những phép đặt lại tên biến như trên (thường chỉ áp dụng cho những biến và câu lệnh đơn) trở nên khó khăn. Chúng ta cần phải tập trung nghiên cứu về khả năng song song của các chu trình vì các chu trình lặp thường hướng tới sự xử lý song song trên các tài nguyên được hỗ trợ. Trong các cấu trúc dữ liệu, mảng được xem là cấu trúc dữ liệu đơn giản và phù hợp nhất cho các chu trình.

Nhưng nếu xem mảng như là một biến trong khi xem xét sự phụ thuộc dữ liệu trong chu trình thì sẽ làm giảm khả năng xử lý song song của các câu lệnh trong chu trình đó. Thật vậy, để thấy rõ được điều này ta có thể xét ví dụ sau:

DO I

A[2*I+1] = B[i] (1)

D[i] = A[2*I] (2)

ENDDO

- Nếu xem mảng A như một thực thể đơn thì (1) và (2) phụ thuộc theo dòng dữ liệu. - Nếu xét thêm chỉ số mảng thì phần tử của mảng A trong câu lệnh (1) là phần tử lẻ còn phần tử của mảng A trong câu lệnh thứ (2) là phần tử chẳn, nên (1) và (2) không có sự phụ thuộc vào dữ liệu.

Xét ví dụ sau: DO I

A[I] = A[I-1] + 1 (*) ENDDO

- Khi I = i : A[i] = A[i-1] + 1 - Khi I = j : A[j] = A[j-1] + 1

Không mất tính tổng quát, ta giả sử rằng i<j, khi đó vòng lặp thứ i và thứ j phụ thuộc dữ liệu với nhau khi và chỉ khi i = j -1. Với i, j là các số nguyên và i < j thì phương trình này có vô số nghiệm. Điều này dẫn đến chu trình (*) không thể thực hiện song song được.

II.1.3.4 Biến đổi chương trình

Như trên đã đề cập, để có được chương trình song song thì phải loại bỏ được sự phụ thuộc của các câu lệnh trong chương trình bằng cách biến đổi các mã lệnh.

Sau đây chúng ta xét một số trường hợp biến đổi mã chương trình để loại bỏ những phụ thuộc dữ liệu trong chương trình.

a- Các biến qui nạp :

Biến qui nạp là loại biến trong chu trình mà các giá trị liên tiếp của nó tạo ra dãy cấp số học.

Ví dụ: Xét đoạn chương trình sau:

m = 0;

DO i = 1 to N

m = m + k; (1){m là biến qui nạp} x[m] = a[i](2)

END

Hiển nhiên là hai câu lệnh (1) và (2) có quan hệ phản phụ thuộc dữ liệu. Khi vòng lặp trước tính giá trị của m, và giá trị này được sử dụng ở lệnh thứ hai thì vòng lặp hiện thời không biết được giá trị của m. Do đó, chúng phải thực hiện tuần tự.

Tuy nhiên, tại mỗi vòng lặp chúng ta có thể dự báo được giá trị của m nên ta cũng có thể loại bỏ được sự phụ thuộc dữ liệu giữa 2 câu lệnh (1) và (2) để từ đó có thể thực hiện song song chu trình trên như sau:

Giá trị k là bất biến trong các vòng lặp và được cộng liên tiếp vào sau mỗi vòng lặp, do vậy, ở vòng lặp thứ i, m = i * k.

Đoạn chương trình trên được viết thành:

DO i = 1 to N x[i*k] = a[i] End

Ta nhận thấy ngay, các vòng lặp của chu trình này không phụ thuộc vào nhau, do vậy chúng có thể thực hiện song song.

Cách biến đổi loại này cho phép chuyển chương trình tuần tự thành chương trình song song. b- Sự phụ thuộc tiến:

Sự phụ thuộc tiến là loại phản phụ thuộc dữ liệu giữa các vòng lặp của chu trình. Ví dụ: Xét chu trình lặp sau:

Do i = 1 to N x[i] = x[i+1] end

Dễ nhận thấy, chu trình này không thực hiện song song được.Chu trình trên làm

Một phần của tài liệu Luận văn nghiên cứu các kiến trúc của máy tính song song, các mô hình và các thuật toán trong xử lý song song, (Trang 25 - 40)

Tải bản đầy đủ (DOC)

(95 trang)
w