Câu trả lời:
Hiệu ứng trên g_qCount là như nhau, nhưng những gì được thực hiện thì khác.
Phần quan trọng của OpenMP là hoàn toàn chung chung - nó có thể bao quanh bất kỳ khối mã tùy ý nào. Tuy nhiên, bạn phải trả cho tính tổng quát đó bằng cách gánh chịu chi phí đáng kể mỗi khi một luồng đi vào và thoát khỏi phần quan trọng (ngoài chi phí vốn có của việc tuần tự hóa).
(Ngoài ra, trong OpenMP, tất cả các phần quan trọng không có tên được coi là giống hệt nhau (nếu bạn thích, chỉ có một khóa cho tất cả các phần quan trọng chưa được đặt tên), vì vậy nếu một luồng nằm trong một phần quan trọng [chưa được đặt tên] như trên, không luồng nào có thể nhập bất kỳ [không tên] phần quan trọng. Như bạn có thể đoán, bạn có thể giải quyết vấn đề này bằng cách sử dụng các phần quan trọng được đặt tên).
Một hoạt động nguyên tử có chi phí thấp hơn nhiều. Nếu có, nó tận dụng lợi thế của phần cứng cung cấp (giả sử) hoạt động gia tăng nguyên tử; trong trường hợp đó, không cần khóa / mở khóa khi nhập / thoát dòng mã, nó chỉ thực hiện gia tăng nguyên tử mà phần cứng cho bạn biết không thể bị can thiệp.
Mặt thuận lợi là chi phí thấp hơn nhiều và một luồng đang trong hoạt động nguyên tử không chặn bất kỳ hoạt động nguyên tử (khác) nào sắp xảy ra. Nhược điểm là tập hợp các hoạt động bị hạn chế mà nguyên tử hỗ trợ.
Tất nhiên, trong cả hai trường hợp, bạn phải chịu chi phí tuần tự hóa.
Trong OpenMP, tất cả các phần quan trọng không có tên loại trừ lẫn nhau.
Sự khác biệt quan trọng nhất giữa quan trọng và nguyên tử là nguyên tử chỉ có thể bảo vệ một nhiệm vụ duy nhất và bạn có thể sử dụng nó với các toán tử cụ thể.
Phần quan trọng:
Có thể được mở rộng để nối tiếp các nhóm khối với việc sử dụng thẻ "tên" thích hợp.
Chậm hơn!
Hoạt động nguyên tử:
Nhanh hơn nhiều!
Chỉ đảm bảo tuần tự hóa một hoạt động cụ thể.
Cách nhanh nhất không quan trọng hay nguyên tử. Tính gần đúng, phép cộng với phần tới hạn đắt gấp 200 lần phép cộng đơn giản, phép cộng nguyên tử đắt hơn phép cộng đơn giản 25 lần.
Tùy chọn nhanh nhất (không phải lúc nào cũng áp dụng) là cung cấp cho mỗi luồng bộ đếm riêng của nó và thực hiện thao tác giảm khi bạn cần tổng tổng.
Những hạn chế của atomic
là quan trọng. Chúng phải được nêu chi tiết về thông số kỹ thuật OpenMP . MSDN cung cấp một bảng gian lận nhanh chóng vì tôi sẽ không ngạc nhiên nếu điều này sẽ không thay đổi. (Visual Studio 2012 có triển khai OpenMP từ tháng 3 năm 2002.) Để trích dẫn MSDN:
Câu lệnh biểu thức phải có một trong các dạng sau:
x
binop =expr
x++
++x
x--
--x
Trong các biểu thức đứng trước:
x
làlvalue
biểu thức có kiểu vô hướng.expr
là một biểu thức có kiểu vô hướng và nó không tham chiếu đến đối tượng được chỉ định bởix
. binop không phải là một nhà điều hành quá tải và là một trong những+
,*
,-
,/
,&
,^
,|
,<<
, hoặc>>
.
Tôi khuyên bạn nên sử dụng atomic
khi bạn có thể và đặt tên các phần quan trọng khác. Đặt tên cho chúng là quan trọng; bạn sẽ tránh gỡ lỗi đau đầu theo cách này.
Đã giải thích tuyệt vời ở đây. Tuy nhiên, chúng ta có thể đi sâu hơn một chút. Để hiểu sự khác biệt cốt lõi giữa khái niệm phần nguyên tử và phần tới hạn trong OpenMP, trước tiên chúng ta phải hiểu khái niệm khóa . Hãy xem lại lý do tại sao chúng ta cần sử dụng ổ khóa .
Một chương trình song song đang được thực thi bởi nhiều luồng. Kết quả xác định sẽ xảy ra nếu và chỉ khi chúng ta thực hiện đồng bộ hóa giữa các luồng này. Tất nhiên, không phải lúc nào cũng cần đồng bộ hóa giữa các luồng. Chúng tôi đề cập đến những trường hợp rằng sự đồng bộ hóa là cần thiết.
Để đồng bộ hóa các luồng trong một chương trình đa luồng, chúng tôi sẽ sử dụng khóa . Khi quyền truy cập được yêu cầu giới hạn bởi chỉ một luồng tại một thời điểm, khóa sẽ phát huy tác dụng. Việc triển khai khái niệm khóa có thể khác nhau giữa các bộ xử lý. Chúng ta hãy tìm hiểu cách một khóa đơn giản có thể hoạt động theo quan điểm thuật toán.
1. Define a variable called lock.
2. For each thread:
2.1. Read the lock.
2.2. If lock == 0, lock = 1 and goto 3 // Try to grab the lock
Else goto 2.1 // Wait until the lock is released
3. Do something...
4. lock = 0 // Release the lock
Thuật toán đã cho có thể được thực hiện bằng ngôn ngữ phần cứng như sau. Chúng tôi sẽ giả định một bộ xử lý duy nhất và phân tích hành vi của các ổ khóa trong đó. Đối với thực hành này, hãy giả sử một trong các bộ xử lý sau: MIPS , Alpha , ARM hoặc Power .
try: LW R1, lock
BNEZ R1, try
ADDI R1, R1, #1
SW R1, lock
Chương trình này có vẻ ổn, nhưng nó không. Đoạn mã trên gặp phải sự cố trước đó; sự đồng bộ hóa . Hãy cùng tìm hiểu vấn đề. Giả sử giá trị ban đầu của khóa là 0. Nếu hai luồng chạy mã này, một luồng có thể đến SW R1, khóa trước khi luồng kia đọc biến khóa . Vì vậy, cả hai người trong số họ nghĩ rằng khóa là miễn phí. Để giải quyết vấn đề này, có một hướng dẫn khác được cung cấp thay vì LW và SW đơn giản . Nó được gọi là lệnh Đọc-Sửa-Viết . Đây là một hướng dẫn phức tạp (gồm subinstructions) mà đảm bảo sự mua lại khóa thủ tục được thực hiện bởi chỉ một đơnchủ đề tại một thời điểm. Sự khác biệt của Read-Modify-Write so với các hướng dẫn Đọc và Viết đơn giản là nó sử dụng một cách Tải và Lưu trữ khác . Nó sử dụng LL (Load Linked) để tải biến khóa và SC (Store Conditional) để ghi vào biến khóa. Một Đăng ký Liên kết bổ sung được sử dụng để đảm bảo quy trình thu thập khóa được thực hiện bởi một luồng duy nhất. Thuật toán được đưa ra dưới đây.
1. Define a variable called lock.
2. For each thread:
2.1. Read the lock and put the address of lock variable inside the Link Register.
2.2. If (lock == 0) and (&lock == Link Register), lock = 1 and reset the Link Register then goto 3 // Try to grab the lock
Else goto 2.1 // Wait until the lock is released
3. Do something...
4. lock = 0 // Release the lock
Khi thanh ghi liên kết được đặt lại, nếu một luồng khác đã cho rằng khóa là tự do, nó sẽ không thể ghi giá trị gia tăng vào khóa một lần nữa. Do đó, có được sự đồng thời của quyền truy cập vào biến khóa .
Sự khác biệt cốt lõi giữa quan trọng và nguyên tử xuất phát từ ý tưởng rằng:
Tại sao phải sử dụng khóa (một biến mới) trong khi chúng ta có thể sử dụng biến thực tế (mà chúng ta đang thực hiện một thao tác trên nó), làm biến khóa?
Sử dụng một biến mới cho khóa sẽ dẫn đến phần quan trọng , trong khi sử dụng biến thực tế làm khóa sẽ dẫn đến khái niệm nguyên tử . Phần quan trọng hữu ích khi chúng tôi thực hiện nhiều phép tính (nhiều hơn một dòng) trên biến thực tế. Đó là bởi vì, nếu kết quả của những phép tính đó không được ghi trên biến thực tế, thì toàn bộ quy trình sẽ được lặp lại để tính toán kết quả. Điều này có thể dẫn đến hiệu suất kém hơn so với việc chờ khóa được giải phóng trước khi vào vùng có tính toán cao. Do đó, bạn nên sử dụng chỉ thị nguyên tử bất cứ khi nào bạn muốn thực hiện một phép tính đơn lẻ (x ++, x--, ++ x, --x, v.v.) và sử dụngchỉ thị quan trọng khi một khu vực phức tạp hơn về tính toán đang được thực hiện bởi phần chuyên sâu.
nguyên tử là hiệu suất tương đối hiệu quả khi bạn cần kích hoạt tính năng loại trừ lẫn nhau cho chỉ một lệnh duy nhất, điều tương tự là không đúng về quan trọng omp.
nguyên tử là một câu lệnh đơn Phần quan trọng, tức là bạn khóa để thực hiện một câu lệnh
phần quan trọng là một khóa trên một khối mã
Một trình biên dịch tốt sẽ dịch mã thứ hai của bạn giống như cách nó làm với mã đầu tiên
++
và*=
) và rằng nếu họ không được hỗ trợ trong phần cứng, họ có thể được thay thế bằngcritical
phần.