Sự khác biệt giữa nguyên tử và quan trọng trong OpenMP là gì?


111

Sự khác biệt giữa nguyên tử và quan trọng trong OpenMP là gì?

tôi có thể làm điều này

#pragma omp atomic
g_qCount++;

nhưng điều này không giống với

#pragma omp critical
g_qCount++;

?

Câu trả lời:


173

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.


5
"bạn có thể mất tính di động" - Tôi không chắc điều này là đúng. Các tiêu chuẩn (phiên bản 2.0) quy định cụ thể mà hoạt động nguyên tử được phép (về cơ bản những thứ như ++*=) và rằng nếu họ không được hỗ trợ trong phần cứng, họ có thể được thay thế bằng criticalphần.
Dan R

@DanRoche: Vâng, bạn nói khá đúng. Tôi không nghĩ rằng câu nói đó đã bao giờ đúng, tôi sẽ sửa nó ngay bây giờ.
Jonathan Dursi

Một vài ngày trước, tôi đã làm theo một hướng dẫn về OpenMP và theo như tôi hiểu, có sự khác biệt trong hai đoạn mã khác nhau. Đó là kết quả có thể khác nhau vì phần quan trọng đảm bảo rằng lệnh được thực thi bởi một luồng tại một thời điểm, tuy nhiên có thể là lệnh: g_qCount = g_qCount + 1; đối với luồng 1 chỉ lưu trữ kết quả g_qCount chỉ trong bộ đệm ghi không có trong bộ nhớ RAM và khi luồng 2 tìm nạp giá trị g_qCount, nó chỉ đọc giá trị trong RAM chứ không phải trong bộ đệm ghi. Đảm bảo việc hướng dẫn nguyên tử hướng dẫn đỏ mặt dữ liệu vào bộ nhớ
Giox79

30

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ể.


13
Đây tốt hơn là một nhận xét (hoặc một chỉnh sửa) của câu trả lời trước đó.
kynan

20

Phần quan trọng:

  • Đảm bảo tuần tự hóa các khối mã.
  • 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ể.


9
Nhưng câu trả lời này là rất có thể đọc được và sẽ là một khoản tiền lớn lên trong những câu trả lời đầu tiên
Michał Miszczyszyn

7

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.


2
Tôi không đồng ý với tất cả các con số bạn đề cập trong giải thích của bạn. Giả sử x86_64, hoạt động nguyên tử sẽ có một vài chi phí chu kỳ (đồng bộ hóa một dòng bộ nhớ cache) với chi phí khoảng một chu kỳ. Nếu ngược lại, nếu bạn có chi phí '' chia sẻ thực sự '', chi phí chung là con số không. Một phần quan trọng phải chịu chi phí của một khóa. Tùy thuộc vào việc khóa đã được thực hiện hay chưa, chi phí chung là khoảng 2 lệnh nguyên tử HOẶC hai lần chạy của bộ lập lịch và thời gian ngủ - thường sẽ nhiều hơn đáng kể 200 lần.
Klaas van Gend

6

Những hạn chế của atomiclà 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:

xbinop =expr

x++

++x

x--

--x

Trong các biểu thức đứng trước: xlvaluebiểu thức có kiểu vô hướng. exprlà 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ởi x. 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 atomickhi 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.


1
Đây không phải là tất cả, chúng tôi có chỉ thị nguyên tử tiên tiến khác như: #pragma OMP cập nhật aromic (hoặc đọc, upate, ghi, chụp) nên nó cho phép chúng ta có một số khác có lợi tuyên bố
pooria

1

Đã 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ử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ì LWSW đơ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 ĐọcViết đơn giản là nó sử dụng một cách TảiLư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ọngnguyê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.


-5

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.


13
Đây chẳng qua là một bản trình bày lại câu trả lời được chấp nhận một cách sơ sài mà không có lời giải thích.
hiệu suất cao

-5

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


Thật tồi tệ. Vui lòng không nói về những thứ bạn không hiểu.
jcsahnwaldt Khôi phục Monica
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.