پیش از C++17، الگوریتم‌های کتابخانه‌ی استاندارد (STL) همگی به‌صورت ترتیبی (Sequential) اجرا می‌شدند و برنامه‌نویسان برای بهره‌گیری از توان پردازنده‌های چند هسته‌ای مجبور به استفاده از کتابخانه‌های سطح پایین مانند <thread> یا <pthread> بودند که نوشتن کد موازی را به کاری پیچیده و مستعد خطا تبدیل می‌کرد . C++17 با معرفی الگوریتم‌های موازی، این مسئله را به‌شکل بنیادین حل کرده است .

با این قابلیت جدید، بسیاری از الگوریتم‌های موجود در هدرهای <algorithm>، <numeric> و <memory> یک امضای جدید دریافت کرده‌اند که یک خط‌مشی اجرایی (Execution Policy) را به‌عنوان اولین پارامتر دریافت می‌کند . این تغییر به ظاهر کوچک، به برنامه‌نویس اجازه می‌دهد تا با تغییر یک پارامتر، از قدرت پردازش موازی استفاده کند؛ بدون اینکه نیازی به مدیریت مستقیم ریسه‌ها یا نگرانی از جزئیات پیاده‌سازی داشته باشد .

معرفی خط‌مشی‌های اجرایی

قلب الگوریتم‌های موازی، چهار نوع خط‌مشی اجرایی است که در هدر <execution> و فضای نام std::execution تعریف شده‌اند . این خط‌مشی‌ها به کتابخانه می‌گویند که چگونه الگوریتم را بهینه‌سازی کند.

خط‌مشی seq: اجرای ترتیبی

std::execution::seq یک خط‌مشی است که به‌صراحت از کتابخانه می‌خواهد تا الگوریتم را به‌صورت دنباله‌ای و در همان ریسه‌ی فراخواننده اجرا کند . استفاده از این خط‌مشی معادل اجرای نسخه‌های قدیمی‌تر و غیرموازی الگوریتم‌هاست و برای مجموعه‌داده‌های کوچک یا عملیات‌هایی که سربار موازی‌سازی از مزایای آن بیشتر است، انتخاب مناسبی محسوب می‌شود . همچنین تضمین می‌کند که هیچ گونه رخداد رقابتی (Data Race) یا بن‌بستی به‌دلیل اجرای همزمان رخ نخواهد داد .

خط‌مشی par: اجرای موازی

std::execution::par به کتابخانه اجازه می‌دهد تا الگوریتم را با استفاده از چندین ریسه (Thread) به‌صورت موازی اجرا کند . در این حالت، عملیات روی عناصر مختلف می‌تواند به‌طور همزمان بر روی چندین هسته‌ی پردازنده توزیع شود. این خط‌مشی برای مجموعه‌داده‌های بزرگ و عملیات‌های سنگین محاسباتی (مانند sort، transform و reduce) بسیار مناسب است و می‌تواند به افزایش چشمگیر سرعت منجر شود . با این حال، مسئولیت جلوگیری از رخدادهای رقابتی و بن‌بست‌ها بر عهده‌ی برنامه‌نویس است .

خط‌مشی par_unseq: موازی و بردارگونه

std::execution::par_unseq یک خط‌مشی ترکیبی است که علاوه بر موازی‌سازی با چندین ریسه، به کتابخانه اجازه می‌دهد تا از دستورات بردارگونه (Vectorized) یا SIMD نیز برای افزایش سرعت استفاده کند . در این حالت، فراخوانی‌های تابع روی عناصر ممکن است به‌طور کاملاً نامرتب و در ریسه‌های مختلف اجرا شوند و حتی ترتیب اجرای آنها در یک ریسه نیز قابل‌پیش‌بینی نیست . این بالاترین سطح از موازی‌سازی را ممکن می‌سازد، اما محدودیت‌های مهمی دارد: توابع فراخوانی‌شده نباید شامل عملیات‌های غیرایمن برای بردارگونه‌سازی مانند std::atomic یا تخصیص حافظه باشند .

نحوه‌ی استفاده و ملاحظات عملکردی

استفاده از الگوریتم‌های موازی به‌سادگی اضافه کردن یک پارامتر خط‌مشی به فراخوانی الگوریتم است . برای مثال، std::sort(std::execution::par, vec.begin(), vec.end()) به‌جای نسخه‌ی معمولی std::sort(vec.begin(), vec.end())، از موازی‌سازی برای مرتب‌سازی بهره می‌برد .

توجه به این نکته ضروری است که موازی‌سازی یک داروی جادویی نیست و همیشه منجر به افزایش سرعت نمی‌شود . سربار ایجاد و مدیریت ریسه‌ها می‌تواند برای مجموعه‌داده‌های کوچک یا عملیات‌های بسیار ساده، از هرگونه مزیت عملکردی بیشتر باشد . به‌عنوان مثال، استفاده از par برای عملیات count_if روی یک میلیارد عنصر، نه تنها سرعت را افزایش نمی‌دهد بلکه ممکن است آن را کاهش دهد . همچنین الگوریتم‌های موازی به تکرارگرهای با دسترسی تصادفی (RandomAccessIterator) نیاز دارند .

مسئولیت برنامه‌نویس در قبال همگام‌سازی

با وجود سهولت استفاده، مسئولیت اصلی در برابر خطاهای همزمانی بر عهده‌ی برنامه‌نویس است . هنگام استفاده از خط‌مشی‌های par و par_unseq، باید از دسترسی همزمان به داده‌های مشترک بدون همگام‌سازی مناسب جلوگیری کرد. به‌عنوان مثال، افزودن عناصر به یک std::vector مشترک از درون یک حلقه‌ی موازی for_each، یک رخداد رقابتی است که می‌تواند به خرابی برنامه منجر شود . برای این منظور باید از ابزارهای همگام‌سازی مناسب مانند std::mutex یا نوع‌های اتمیک استفاده کرد، اما با خط‌مشی par_unseq استفاده از std::atomic مجاز نیست .

کلیدواژه ها : C++17 parallel algorithms-الگوریتم‌های موازی C++17-std::execution::par-std::execution::par_unseq-std::execution::seq-خط مشی اجرایی C++17-SIMD در C++17-parallel STL C++17-بهبود عملکرد با موازی‌سازی-execution policy header-execution policy types C++17-استفاده از std::for_each موازی-هدر execution در C++17-مقایسه seq و par و par_unseq-parallel sort C++17-عملکرد الگوریتم‌های موازی-مسئولیت برنامه‌نویس در موازی‌سازی