Những phụ thuộc và những rủi ro về dữ liệu

Một phần của tài liệu Kiến trúc máy tính tiên tiến (Trang 51 - 54)

KHỐI XỬ LÝ SONG SONG Ở MỨC LỆNH MÁY

2.1. Kỹ thuật đường ống và xử lý song song ở mức lệnh máy

2.1.2. Các khó khăn khi sử dụng đường ống và cách khắc phục

2.1.2.1. Những phụ thuộc và những rủi ro về dữ liệu

Việc xác định một lệnh phụ thuộc vào một lệnh khác như thế nào thì rất quan trọng để xác định có bao nhiêu sự song song tồn tại trong một chương trình và làm thế nào mà sự song song đó có thể được khai thác. Đặc biệt, để khai thác khả năng thực hiện lệnh song song chúng ta phải xác định những lệnh nào có thể được thực hiện song song. Nếu có hai lệnh là song song, chúng có thể thực hiện đồng thời trong một đường ống với độ sâu tùy ý mà không gây ra bất kỳ ảnh hưởng nào, giả sử các đường ống này có đủ nguồn lực (và do đó không tồn tại những nguy cơ cấu trúc). Nếu là hai lệnh phụ thuộc, thì chúng không song song và phải được thực hiện trong trật tự, mặc dù chúng thường có thể được chồng chéo từng phần. Điều quan trọng trong cả hai trường hợp là để xác định xem liệu một lệnh có phụ thuộc vào lệnh khác hay không.

Nhng s ph thuc d liu

Có ba loại phụ thuộc khác nhau: phụ thuộc dữ liệu (còn gọi là phụ thuộc dữ liệu thực), phụ thuộc tên và phụ thuộc điều khiển. Một lệnh j là dữ liệu phụ thuộc vào lệnh i nếu chứa một trong hai điều sau đây:

• Lệnh i tạo ra một kết quả có thể được sử dụng bởi lệnh j, hoặc

• Lệnh j là dữ liệu phụ thuộc vào lệnh k và lệnh k là dữ liệu phụ thuộc vào lệnh i.

Điều kiện thứ hai chỉ đơn giản nói rằng một lệnh phụ thuộc vào lệnh khác nếu tồn tại một chuỗi các phụ thuộc của loại đầu tiên giữa hai lệnh.

Chuỗi phụ thuôc này có thể chỉ cần là toàn bộ chương trình. Lưu ý rằng một sự phụ thuộc trong vòng một chỉ dẫn duy nhất (chẳng hạn như ADDD R1, R1, R1) không được coi là một phụ thuộc.

Ví dụ, hãy xem xét đoạn mã MIPS sau đây nói về việc tăng các giá trị của vectơ trong bộ nhớ (bắt đầu từ 0(R1) và với phần tử cuối cùng 8(R2)), bằng một đại lượng vô hướng trong thanh ghi F2. (Để đơn giản, trong suốt chương này, những ví dụ của chúng ta bỏ qua những tác động của các nhánh trễ).

Loop: L.D F0,0(R1) ;F0 = phần tử mảng

ADD.D F4,F0,F2 ;thêm đại lượng vô hướng vào F2 S.D F4,0(R1) ;lưu trữ kết quả

DADDUI R1,R1,#-8 ;độ giảm con trỏ 8 bytes BNE R1,R2,Loop ;nhánh R1!=R2

Các phụ thuộc dữ liệu trong đoạn mã này liên quan đến hai dấu chấm động:

Loop: L.D F0,0(R1) ;F0= phần tử mảng

ADD.D F4, F0,F2 ;thêm đại lượng vô hướng vào F2 S.D F4,0(R1) ;lưu trữ kết quả

và dữ liệu số nguyên:

DADDIU R1,R1,-8 ;độ giảm con trỏ

;8 bytes (per DW) BNE R1,R2,Loop ;nhánh R1!=R2

Cả hai chuỗi phụ thuộc ở trên, được thể hiện bởi các mũi tên, có mỗi lệnh phụ thuộc vào lệnh trước đó. Những mũi tên ở đây trong những ví dụ thể hiện thứ tự phải được bảo đảm cho việc thi hành đúng. Mũi tên chỉ từ một lệnh phải đi trước một lệnh khác, mà đầu mũi tên đó chỉ tới.

Nếu hai lệnh là dữ liệu phụ thuộc, thì chúng không thể thực hiện đồng thời hoặc được chồng lấp hoàn toàn. Những sự phụ thuộc hàm ý rằng sẽ có một chuỗi một hoặc nhiều những rủi ro về dữ liệu giữa hai lệnh. Việc thực hiện các lệnh đồng thời sẽ tạo ra những khóa trong đường ống lệnh của bộ xử lý (và chiều sâu một đường ống dài hơn khoảng cách giữa các lệnh trong chu kỳ) để phát hiện rủi ro và sự chậm trễ, do đó làm giảm hoặc loại bỏ sự chồng chéo. Trong một bộ xử lý mà không có những khóa dựa vào việc lập lịch của trình biên dịch, thì trình biên dịch không thể lập lịch những lệnh phụ thuộc theo cách mà chúng hoàn toàn chồng chéo lên nhau, vì thế chương trình sẽ không thực hiện đúng. Sự hiện diện của một sự phụ thuộc dữ liệu trong một dãy lệnh phản ánh một sự phụ thuộc dữ liệu trong mã nguồn từ đó dãy lệnh được tạo ra. Hiệu quả của sự phụ thuộc dữ liệu ban đầu phải được bảo đảm.

Những sự phụ thuộc là một đặc tính của chương trình. Liệu những kết quả của một sự phụ thuộc được đưa ra trong một rủi ro thực sự có bị phát hiện hay không và liệu rủi ro thực sự đó gây ra một sự ngưng trì hoãn có phải là đặc tính của tổ chức đường ống hay không. Sự khác biệt này là rất quan trọng để hiểu làm thế nào mà khả năng thực hiện lệnh song song được khai thác.

Một sự phụ thuộc dữ liệu truyền tải ba điều: (1) khả năng của một rủi ro, (2) những kết quả phải được tính toán theo thứ tự và (3) mức giới hạn bao nhiêu song song có thể được khai thác. Những giới hạn như vậy sẽ được khám phá trong Chương 3.

Vì một sự phụ thuộc dữ liệu có thể giới hạn số lượng khả năng thực hiện lệnh song song chúng ta có thể khai thác, nên tập trung chủ yếu của chương này là khắc phục những hạn chế đó. Một sự phụ thuộc có thể được khắc phục theo hai cách

khác nhau: duy trì sự phụ thuộc nhưng tránh gây nguy hiểm và loại trừ một sự phụ thuộc bằng cách biến đổi mã. Lập lịch một đoạn mã là phương pháp chính được sử dụng để tránh gây rủi ro mà không thay đổi một sự phụ thuộc và việc lập lịch như vậy có thể được thực hiện bởi trình biên dịch và phần cứng.

Giá trị dữ liệu có thể bắt nguồn giữa các lệnh hoặc thông qua các thanh ghi hoặc thông qua vị trí bộ nhớ. Khi dòng dữ liệu xảy ra trong một thanh ghi, việc phát hiện sự phụ thuộc là đơn giản vì các tên thanh ghi được ấn định trong các lệnh, mặc dù nó sẽ phức tạp hơn khi có những nhánh can thiệp và sự chính xác thì liên quan đến sức mạnh trình biên dịch hoặc phần cứng có được bảo toàn.

Những phụ thuộc bắt nguồn thông qua vị trí bộ nhớ thì có nhiều khó khăn để phát hiện, vì hai địa chỉ có thể là cùng một vị trí nhưng nhìn khác nhau: Ví dụ, 100 (R4) và 20 (R6) có thể là những địa chỉ bộ nhớ giống hệt nhau. Ngoài ra, những địa chỉ thực trong việc nạp và lưu trữ có thể thay đổi từ một sự thi hành lệnh này đến một sự thi hành lệnh khác, việc phát hiện một phụ thuộc sẽ phức tạp hơn.

Trong chương này, chúng tôi kiểm tra phần cứng để phát hiện những phụ thuộc dữ liệu liên quan đến vị trí bộ nhớ, nhưng chúng ta sẽ thấy rằng những kỹ thuật này cũng có những hạn chế. Các kỹ thuật biên dịch để phát hiện những phụ thuộc đó thì quan trọng trong việc phát hiện ra vòng lặp cấp song song.

Nhng ph thuc tên

Loại thứ hai của sự phụ thuộc là phụ thuộc tên. Một phụ thuộc tên xảy ra khi hai lệnh sử dụng cùng một thanh ghi hoặc vị trí bộ nhớ, được gọi là một tên, nhưng không bắt nguồn từ dữ liệu giữa các chỉ lệnh liên kết với tên. Có hai loại phụ thuộc tên giữa một lệnh i đi trước chỉ lệnh j theo tuần tự chương trình:

1. Sự không phụ thuộc giữa chỉ lệnh i và j xảy ra khi chỉ lệnh j ghi vào thanh ghi hoặc vị trí bộ nhớ đó cái mà chỉ lệnh i sẽ đọc. Các trật tự ban đầu phải được bảo đảm để chắc rằng i đọc đúng giá trị. Trong ví dụ trước, có một sự không phụ thuộc giữa S.D và DADDIU trong thanh ghi R1.

2. Sự phụ thuộc vào việc xuất dữ liệu xảy ra khi lệnh i và j ghi vào cùng một thanh ghi hoặc vị trí bộ nhớ. Trật tự giữa các lệnh phải được bảo đảm để chắc rằng giá trị cuối cùng được ghi ra tương ứng với lệnh j.

Sự không phụ thuộc và phụ thuộc đầu ra đều là những phụ thuộc tên, đối lập với những phụ thuộc dữ liệu thực, vì không có giá trị được gửi đi giữa các lệnh. Vì một phụ thuộc tên không phải là một sự phụ thuộc thật, nên những lệnh liên quan vào sự phụ thuộc tên có thể thực thi đồng thời hoặc được sắp xếp lại, nếu tên (số thanh ghi hoặc vị trí bộ nhớ) đã sử dụng trong các lệnh được thay đổi thì những lệnh này sẽ không xung đột với nhau.

Việc đổi tên này có thể được thực hiện dễ dàng hơn với các toán hạng về thanh ghi, nó được gọi là sự đổi tên thanh ghi. Sự đổi tên thanh ghi có thể được thực hiện bằng phương pháp tĩnh bởi trình biên dịch hoặc động bởi phần cứng. Trước khi miêu tả sự phụ thuộc phát sinh từ các nhánh, chúng ta hãy xem xét mối quan hệ giữa những sự phụ thuộc và những rủi ro dữ liệu trong đường ống.

Một phần của tài liệu Kiến trúc máy tính tiên tiến (Trang 51 - 54)

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

(260 trang)