مرا به خاطر بسپار

OpenMP چیست؟

بازدید: 149 آخرین به‌روزرسانی: 27 مرداد 1405

مقدمه

پردازنده‌ی کامپیوتر شما احتمالاً ۸، ۱۶ یا حتی تعداد بیشتری هسته دارد؛ اما آیا برنامه‌ای که می‌نویسید واقعاً از همه‌ی این هسته‌ها استفاده می‌کند، یا فقط یکی از آن‌ها کار می‌کند و بقیه بیکار می‌مانند؟ در بسیاری از برنامه‌های معمولی، پاسخ متأسفانه گزینه‌ی دوم است. اینجاست که OpenMP وارد می‌شود و به ساده‌ترین شکل ممکن به شما اجازه می‌دهد قدرت همه‌ی هسته‌های پردازنده را آزاد کنید.

در این مقاله به زبان ساده توضیح می‌دهیم که OpenMP چیست، چه تفاوتی با MPI دارد، بر چه مدلی استوار است و چگونه می‌توانید تنها با افزودن چند خط، یک برنامه‌ی معمولی را به یک برنامه‌ی موازی تبدیل کنید.

OpenMP چیست؟

OpenMP (مخفف Open Multi-Processing) یک واسط برنامه‌نویسی کاربردی (API) استاندارد و متن‌باز برای نوشتن برنامه‌های موازی روی سیستم‌های حافظه‌اشتراکی است. این استاندارد برای زبان‌های C، C++ و Fortran طراحی شده و امروزه در تقریباً همه‌ی کامپایلرهای مدرن مانند GCC و Clang پشتیبانی می‌شود.

ویژگی متمایز OpenMP سادگی آن است: به‌جای بازنویسی کامل برنامه، شما با افزودن دستورهای خاصی به نام دایرکتیو (که با pragma omp# شروع می‌شوند) به کامپایلر می‌گویید کدام بخش از برنامه باید موازی اجرا شود. این یعنی می‌توانید به‌صورت تدریجی و گام‌به‌گام برنامه‌ی موجود خود را موازی کنید، بدون آنکه ساختار کلی آن را به هم بزنید.
 

چرا OpenMP و تفاوت آن با MPI

در دنیای محاسبات موازی دو مدل اصلی وجود دارد و تفاوت OpenMP و MPI دقیقاً در همین‌جاست:
    • حافظه‌ی اشتراکی (OpenMP)همه‌ی نخ‌ها به یک حافظه‌ی مشترک دسترسی دارند. مناسب برای یک ماشین (یک گره) با چند هسته. ساده و سریع برای شروع.

    • حافظه‌ی توزیع‌شده (MPI)هر پردازه حافظه‌ی مستقل خود را دارد و داده‌ها از طریق پیام میان گره‌ها رد و بدل می‌شوند. مناسب برای اجرا روی چندین گره‌ی یک کلاستر.


به بیان ساده، OpenMP برای موازی‌سازی درون یک گره (بین هسته‌های یک پردازنده) عالی است، در حالی که MPI برای گسترش محاسبات روی چندین گره به  کار می‌رود. در بسیاری از برنامه‌های بزرگ علمی، این دو با هم و به‌صورت ترکیبی (Hybrid MPI+OpenMP) استفاده می‌شوند:
  • MPI   کار را بین گره‌ها تقسیم می‌کند و
  •  OpenMP درون هر گره از همه‌ی هسته‌ها بهره می‌برد.

مفاهیم کلیدی و مدل اجرای OpenMP

OpenMP بر پایه‌ی مدلی به نام انشعاب-پیوند (Fork-Join) کار می‌کند. مفاهیم اصلی عبارت‌اند از:

  • نخ اصلی (Master Thread): برنامه به‌صورت ترتیبی و با یک نخ آغاز می‌شود.
  • انشعاب (Fork): وقتی برنامه به یک ناحیه‌ی موازی می‌رسد، نخ اصلی چند نخ فرعی ایجاد می‌کند و کار میان آن‌ها تقسیم می‌شود.
  • پیوند (Join): پس از پایان ناحیه‌ی موازی، نخ‌های فرعی متوقف می‌شوند و برنامه دوباره با نخ اصلی به‌صورت ترتیبی ادامه می‌یابد.
  • متغیرهای اشتراکی و خصوصی (Shared/Private): برخی متغیرها میان همه‌ی نخ‌ها مشترک‌اند و برخی برای هر نخ نسخه‌ی جداگانه دارند؛ مدیریت درست این موضوع برای صحت نتیجه حیاتی است.

 

نوشتن اولین برنامه OpenMP

زیبایی OpenMP در این است که با یک خط می‌توانید یک حلقه را موازی کنید. مثال زیر یک حلقه‌ی ساده را روی همه‌ی هسته‌ها توزیع می‌کند:

#include 
#include 
 
int main() {
    #pragma omp parallel for
    for (int i = 0; i < 8; i++) {
        printf("Iteration %d by thread %d\n",
               i, omp_get_thread_num());
    }
    return 0;
}

خط pragma omp parallel for# به کامپایلر می‌گوید که تکرارهای این حلقه را میان نخ‌های موجود تقسیم کند. تابع ()omp_get_thread_num شماره‌ی نخی را که هر تکرار را اجرا می‌کند برمی‌گرداند. برای کامپایل این برنامه کافی است پرچم fopenmp- را به کامپایلر بدهید:

gcc -fopenmp hello_omp.c -o hello_omp

همچنین می‌توانید با تنظیم متغیر محیطی OMP_NUM_THREADS تعداد نخ‌های مورد استفاده را کنترل کنید؛ برای مثال دستور زیر برنامه را با ۴ نخ اجرا می‌کند:

OMP_NUM_THREADS=4 ./hello_omp

اجرای OpenMP روی کلاستر و ابر سیمرغ

هنگام اجرای برنامه‌های OpenMP روی یک کلاستر، معمولاً از زمان‌بند بار کاری (مانند SLURM  برای خواندن مقاله روی کلمه‌ی اشاره شده کلیک کنید.) استفاده می‌شود. نکته‌ی کلیدی این است که چون OpenMP از حافظه‌ی اشتراکی استفاده می‌کند، باید به SLURM اعلام کنید که تعداد مشخصی هسته (CPU) روی یک گره به کار شما اختصاص دهد. این کار با گزینه‌ی --cpus-per-task انجام می‌شود.

نمونه‌ی زیر یک اسکریپت SLURM برای اجرای برنامه‌ی OpenMP با ۸ هسته است:

#!/bin/bash
#SBATCH --job-name=openmp_job
#SBATCH --nodes=1
#SBATCH --ntasks=1
#SBATCH --cpus-per-task=8
#SBATCH --time=00:30:00
#SBATCH --output=omp_%j.log
 
export OMP_NUM_THREADS=$SLURM_CPUS_PER_TASK
srun ./hello_omp

در این اسکریپت، متغیر OMP_NUM_THREADS به‌طور خودکار برابر با تعداد هسته‌های تخصیص‌ یافته توسط SLURM تنظیم می‌شود تا برنامه از همه‌ی هسته‌ها استفاده کند. ابر سیمرغ با ارائه‌ی گره‌های چندهسته‌ای قدرتمند، بستر مناسبی برای اجرای برنامه‌های OpenMP و ترکیبی MPI+OpenMP فراهم می‌کند؛ می‌توانید خدمات پردازشی موردنیاز خود را از ابر سیمرغ تهیه کرده و کارهای موازی سنگین را با کارایی بالا اجرا کنید. برای آشنایی بیشتر به وب‌سایت ابر سیمرغ مراجعه کنید.

جمع‌بندی

OpenMP ساده‌ترین و سریع‌ترین راه برای بهره‌بردن از قدرت پردازنده‌های چندهسته‌ای است. تنها با افزودن چند دایرکتیو می‌توانید برنامه‌های موجود را موازی کنید و سرعت اجرای آن‌ها را چند برابر افزایش دهید. درک مدل Fork-Join و مدیریت درست متغیرهای اشتراکی و خصوصی، کلید نوشتن برنامه‌های موازی صحیح و کارآمد است.

در مقاله‌های بعدی به موضوعات پیشرفته‌تری مانند همگام‌سازی و جلوگیری از شرایط رقابتی (Race Condition)، زمان‌بندی حلقه‌ها و برنامه‌نویسی ترکیبی MPI+OpenMP خواهیم پرداخت.

سوالات متداول

  • آیا OpenMP جایگزین MPI است؟
خیر، این دو مکمل یکدیگرند. OpenMP برای موازی‌سازی درون یک گره (حافظه‌ی اشتراکی) و MPI برای توزیع کار میان چند گره (حافظه‌ی توزیع‌شده) به کار می‌رود و اغلب با هم استفاده می‌شوند.

  • برای استفاده از OpenMP به چه چیزی نیاز دارم؟
تنها به یک کامپایلر سازگار (مانند GCC یا Clang) و افزودن پرچم fopenmp- هنگام کامپایل نیاز دارید؛ نیازی به نصب کتابخانه‌ی جداگانه نیست.

  • شرایط رقابتی (Race Condition) چیست؟
وقتی چند نخ به‌طور هم‌زمان یک متغیر مشترک را تغییر می‌دهند و نتیجه به ترتیب اجرا وابسته می‌شود، شرایط رقابتی رخ می‌دهد. OpenMP ابزارهایی مانند critical و reduction برای جلوگیری از آن دارد.

  • چطور تعداد نخ‌ها را کنترل کنم؟
با تنظیم متغیر محیطی OMP_NUM_THREADS یا با استفاده از تابع ()omp_set_num_threads درون برنامه.
تا چه حد این مطلب برای شما مفید بود؟
بر اساس رای 0 نفر

اگر بازخوردی درباره این مطلب دارید یا پرسشی دارید که بدون پاسخ مانده است، آن را از طریق بخش نظرات مطرح کنید.

ثبت نظر

نظر دادن