[OpenMP-Phần 2] Viết Chương trình có OpenMP

[bài viết được dịch từ http://bisqwit.iki.fi/story/howto/openmp/%5D

Cú Pháp:

Tất cả các directives trong C/C++ đều bắt đầu bằng #pragma omp và theo sau bởi các tham số, kết thúc bằng dấu xuống dòng ( dòng mới).
pragma được dùng với hầu hết các statement theo ngay sau nó, trừ barrierfflush.

parallel pragma

pragma parallel bắt đầu các khối song song. Nó tạo ra các team với N threads. Sau khối song song này, các threads lại gộp lại.

#pragma omp parallel
{
    //code trong vùng này sẽ chạy song song.
    printf("Hello!\n");
}

Vòng lặp for

directive for sẽ chia nhỏ vòng lặp for ra nên mỗi threads trong team sẽ chạy các phần nhỏ trong vòng lặp:

#pragma omp parallel for
for(int n=0; n<10; ++n)
{
    printf(" %d", n);
}
printf(".\n");

Đoạn code trên in ra các số từ 0 đến 9, Vì các threads là ngẫu nhiên nên thứ tự in ra sẽ là ngẫu nhiên, ta không thể đoán trước được, ví dụ như: 0 5 6 7 1 8 2 3 4 9.

Hoặc ta có thể chỉ định số threads được phép chạy bằng cách thêm vào sau chỉ định parallel thành như sau:

#pragma omp parallel for num_threads(3)
  • Lưu ý nhỏ: C++ hỗ trợ khai báo chỉ số chạy trong vòng lặp for nên n là riêng cho các threads, nhưng C thì không cho phép khai báo như vậy nên ta phải chỉ định n là biến riêng của từng threads bằng thuộc tính: private(n). Chương trình trở thành:
int n;
#pragma omp parallel for num_threads(3) private(n)
for(n=0; n<10; ++n)
{
    printf(%d",n);
}
printf(".\n");

Vậy parallel , for, parallel for là gì?

  • Parallel tạo ra thêm các threads mới để thực thi công việc, nếu chương trình không có câu lệnh parallel thì nó chỉ có 1 thread và hoạt động giống như chương trình bình thường.
  • for chia công việc của vòng lặp for cho các threads
  • parallel for: vừa tạo thêm các threads và chia công việc của vòng lặp cho các threads, parallel for là dạng viết tắt của 2 directive trên.

schedule

  • Cú pháp:
#pragma omp parallel for schedule(static|dynamic,...)

static là tham số mặc định của schedule. Khi vào vòng lặp, mỗi threads sẽ quyết định xem phần nào của vòng lặp mà nó sẽ chạy.
ví dụ:

#pragma omp for schedule(static)
for(int n=0; n<10; ++n) printf(" %d", n);
printf(".\n");

đối với dynamic, ta không thể dự đoán được chỉ số của vòng lặp gán cho các threads khác nhau. Mỗi threads sẽ yêu cầu OpenMP một lượng vòng chạy, sau khi chạy xong thì nó sẽ yêu cầu tiếp, cho đến khi nào hết. Điều này có thể hữu ích khi các vòng lặp khác nhau mất các khoảng thời gian khác nhau để hoàn thành.
Ta cũng có thể gán số vòng lặp cho mỗi threads khi nó yêu cầu:

#pragma omp for schedule(dynamic, 3)
for(int n=0; n<10; ++n) printf(" %d", n);
printf(".\n");

Trong ví dụ này, mỗi threads sẽ yêu cầu chạy 3 vòng lặp rồi yêu cầu tiếp… kích thước mảng cuối cùng có thể ít hơn 3.

Ordered

Thứ tự trong vòng lặp là không xác định, tùy từng trường hợp. Tuy nhiên, ta có thể ép buộc các sự kiện cụ thể trong vòng lặp theo một thứ tự định trước, khi đó là sử dụng mệnh đề ordered.

 #pragma omp for ordered schedule(dynamic)
 for(int n=0; n<100; ++n)
 {
   files[n].compress();

   #pragma omp ordered
   send(files[n]);
 }

Đoạn mã trên thực hiện thao tác nén 100 files, và một số file thì được nén song song, nhưng chắc chắn rằng các file được gửi theo một thứ tự nghiêm ngặt.
Ví dụ: một thread được xác định là phải nén file 7 trong khi file 6 chưa hoàn thành, thread sẽ chờ cho tới khi file 6 được nén xong rồi mới gửi đi, rồi mới nhận file khác để nén.

Section

Đôi khi trong chương trình, không phải là trong vòng lặp for nhưng các công việc vẫn có thể chạy song song với nhau. section được thiết kế để thực hiện việc này.

#pragma omp sections
 {
   { Work1(); }
   #pragma omp section
   { Work2();
     Work3(); }
   #pragma omp section
   { Work4(); }

Trong sơ đồ trên, #pragma omp sections ( lưu ý là có s) để thông báo rằng cụm sau đây có thể thực hiện đồng thời.
Sau đó các cụm được thực hiện đồng thời sẽ được đặt trong một block :

#pragma omp section
{
    //work is placed here!
}

trong ví dụ trên, OpenMP sẽ biết rằng work 1 có thể chạy song song với (work2+work3) và work 4, work 2 và work 3 không chạy song song được mà phải theo thứ tự.
Mỗi work được thực hiện đúng 1 lần.
== Lưu ý ==: #pragma omp sections chỉ báo rằng các sections cho các threads khác nhau trong team hiện tại. Để tạo ra một nhóm threads thì ta cần thêm từ khóa parallel

 #pragma omp parallel sections // starts a new team
 {
   { Work1(); }
   #pragma omp section
   { Work2();
     Work3(); }
   #pragma omp section
   { Work4(); }
 }

Hoặc:

 #pragma omp parallel // starts a new team
 {
   Work0(); // this function would be run by all threads.

   #pragma omp sections // divides the team into sections
   { 
     // everything herein is run only once.
     { Work1(); }
     #pragma omp section
     { Work2();
       Work3(); }
     #pragma omp section
     { Work4(); }
   }

   Work5(); // this function would be run by all threads.
 }

atomic

critical

flush*

        /* presumption: int a = 0, b = 0; */

    /* First thread */                /* Second thread */
    b = 1;                            a = 1;
    #pragma omp flush(a,b)            #pragma omp flush(a,b)
    if(a == 0)                        if(b == 0)
    {                                 {
      /* Critical section */            /* Critical section */
    }        

Trong ví dụ này, ở thời điểm mà biến a,b được dùng thì đảm bảo rằng chúng đã được cập nhật.
Ta cần flush khi ta ghi hoặc đọc từ một dữ liệu chung ở các threads khác nhau.

  • Lưu ý: Nếu chương trình hoạt động đúng mà không có flush thì không có nghĩa rằng flush không cần.
    Ta cần flush bất cứ khi nào ta muốn truy cập vào biến shared trong nhiều threads: **sau khi ghi và trước khi đọc **.

kiểm soát dữ liệu nào được shared giữa các threads.

Trong vùng chạy song song, có thể chỉ định biến nào là shared giữa các threads, biến nào là private cho từng threads. Mặc định tất cả các biến đều là shared, trừ những khai báo trong các khối song song.

  • mệnh đề: **private, firstprivate, shared **
 int a, b=0;
 #pragma omp parallel for private(a) shared(b)
 for(a=0; a<50; ++a)
 {
   #pragma omp atomic
   b += a;
 }

Trong ví dụ này, ta chỉ định a là private(mỗi threads sẽ có bản copy riêng của mình, các threads khác không tác động đến được), b là shared ( các threads đều có thể tác động lên biến này )

  • sự khác nhau giữa firstprivate và private
    private chỉ copy tên biến, kiểu dữ liệu chứ không khởi tạo ( copy giá trị ) của biến đó.
    ví dụ:
 #include <string>
 #include <iostream>

 int main()
 {
     std::string a = "x", b = "y";
     int c = 3;

     #pragma omp parallel private(a,c) shared(b) num_threads(2)
     {
         a += "k";
         c += 7;
         std::cout << "A becomes (" << a << "), b is (" << b << ")\n";
     }
 }

Kết quả mà ta nhận được là (k) chứ không phải (xk) như ta mong đợi.
Nếu ta thực sự muốn tạo ra một bản sao của biến cho mỗi threads ( bao gồm cả giá trị ) thì ta sẽ sử dụng mệnh đề firstprivate

 #include <string>
 #include <iostream>

 int main()
 {
     std::string a = "x", b = "y";
     int c = 3;

     #pragma omp parallel firstprivate(a,c) shared(b) num_threads(2)
     {
         a += "k";
         c += 7;
         std::cout << "A becomes (" << a << "), b is (" << b << ")\n";
     }
 }
  • mệnh đề reduction:
    mệnh đề reduction là tổ hợp của private, shared, atomic. Reduction cho phép tính tích lũy của biến mà không sử dụng mệnh đề atomic, phép tính tích lũy cần phải được chỉ rõ. Mệnh đề reduction thực hiện nhanh hơn mệnh đề atomic
 int factorial(int number)
 {
   int fac = 1;
   #pragma omp parallel for reduction(*:fac)
   for(int n=2; n<=number; ++n)
     fac *= n;
   return fac;
 }
  • ở khởi đầu của vùng song song, bản copy private của biến và không được khởi tạo trước giá trị.
  • Ở cuối của vùng song song, các biến private được gộp lại vào biến shared sử dụng các phép toán đã định sẵn ở trên.
    (bản private copy thực sự là một biến local với cùng tên và cùng kiểu, giá trị ban đầu của nó thì không được khởi tạo bởi giá trị cũ mà theo bảng sau)
Operator Initialization value
+,-,^, 0
*,&& 1
& ~0

synchronization

  • barrier directive và nowait clause

barrier directive làm ch tất cả các threads gặp barrier và chờ cho tới khi các threads khác trong cùng team hoàn thành thì mới thực hiện tiếp.

 #pragma omp parallel
 {
   /* All threads execute this. */
   SomeCode();

   #pragma omp barrier

   /* All threads execute this, but not before
    * all threads have finished executing SomeCode().
    */
   SomeMoreCode();
 }

Lưu ý: luôn có barrier ở cuối mỗi vùng song song, cuối của sections, câu lệnh for và single nếu nowait không được dùng.


 #pragma omp parallel
 {
   #pragma omp for
   for(int n=0; n<10; ++n) Work();

   // This line is not reached before the for-loop is completely finished
   SomeMoreCode();
 }

 // This line is reached only after all threads from
 // the previous parallel block are finished.
 CodeContinues();

 #pragma omp parallel
 {
   #pragma omp for nowait
   for(int n=0; n<10; ++n) Work();

   // This line may be reached while some threads are still executing the for-loop.
   SomeMoreCode();
 }

 // This line is reached only after all threads from
 // the previous parallel block are finished.
 CodeContinues();

nowait chỉ có thể đi kèm với sections, for, single. Nó không thể kèm với mệnh đề ordered.


single và master directive

single directive sẽ yêu câu block đó chỉ chạy 1 threads, không yêu cầu là thread nào. Các threads khác sẽ chờ ở barrier nơi kết thúc của block đó.

 #pragma omp parallel
 {
   Work1();
   #pragma omp single
   {
     Work2();
   }
   Work3();
 }

Trong ví dụ trên, openMP được cài với 2 threads chẳng hạn thì work1 sẽ chạy 2 lần, work2 chạy 1 lần và work 3 chạy 2 lần.
có một barrier ở cuối của single, nhưng không có ở đầu.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Create a free website or blog at WordPress.com.

Up ↑

mlcvGru

Random thoughts on Machine Learning, Deep Learning and (sometimes) Computer Vision

LazyT

Góc nhỏ của Quyền

phanlan

we get what we give, sống là cho đi

Codeaholicguy

software engineer, team lead at #kobiton, blogger at @codeaholicguy

Maths 4 Physics & more...

Blog Toán Cao Cấp (M4Ps)

Vatlyvietnam's Blog

Thế Giới Song Song

Darren Wilkinson's research blog

Statistics, computing, data science, Bayes, stochastic modelling, systems biology and bioinformatics

Ông Xuân Hồng

Chia sẻ kiến thức và thông tin về Machine learning

Từ coder đến developer - Tôi đi code dạo

Lập trình viên giỏi không phải chỉ biết code

Computational Biology and Molecular Modelling

An interface between biology, chemistry and computer science

Moriator - I can do it!

Linux dễ dàng hơn bạn nghĩ!

VinaCode

Lập trình & Cuộc sống

Blog của Chiến

Học. Thực hành. Sáng tạo

Bespoke Blog

Science! Culture! Computational Engines!

Vuhavan's Blog

Just another WordPress.com weblog

%d bloggers like this: