C++17 با معرفی سه سیاست اجرایی در هدر <execution>، امکان کنترل نحوهی اجرای الگوریتمهای کتابخانهی استاندارد را فراهم کرده است . این سیاستها که در فضای نام std::execution
تعریف شدهاند، به برنامهنویس اجازه میدهند تا با تغییر یک پارامتر
ساده، از مزایای پردازش موازی بهرهمند شود، بدون اینکه نیاز به مدیریت
مستقیم ریسهها داشته باشد .
هر یک از این سیاستها، سطح مشخصی از موازیسازی و برداریسازی را مجاز
میدانند و محدودیتهای خاص خود را بر رفتار کد تحمیل میکنند.
seq: اجرای ترتیبی و قابلپیشبینی
سیاست std::execution::seq یا ترتیبی، از کتابخانه میخواهد تا الگوریتم را بهصورت دنبالهای و در همان ریسهی فراخواننده اجرا کند . فراخوانیهای توابع روی عناصر، بهصورت نامعین دنبالهدار در یک ریسه اجرا میشوند، به این معنا که ترتیب نسبی آنها مشخص نیست اما روی همدیگر تأثیر نمیگذارند .
این خطمشی معادل اجرای نسخههای قدیمیتر و غیرموازی الگوریتمهاست و
برای مجموعهدادههای کوچک یا عملیاتهایی که سربار موازیسازی از مزایای
آن بیشتر است، انتخاب مناسبی محسوب میشود. همچنین استفاده از seq تضمین میکند که هیچ رخداد رقابتی یا بنبستی بهدلیل اجرای همزمان رخ نخواهد داد .
par: اجرای موازی با چندین ریسه
سیاست std::execution::par یا موازی، به کتابخانه اجازه میدهد تا الگوریتم را با استفاده از چندین ریسه بهصورت همزمان اجرا کند .
فراخوانیهای توابع روی عناصر مختلف میتوانند در ریسههای مختلفی اجرا
شوند و حتی ممکن است در همان ریسهی فراخواننده نیز اجرا گردند . تفاوت مهم par با seq در این است که ضمانت پیشرفت موازی
را فراهم میکند، به این معنا که اگر یک ریسه یک قدم بردارد، در نهایت قدم
دیگری نیز خواهد برداشت. این ویژگی به ریسهها اجازه میدهد تا وارد
بخشهای بحرانی شده و قفلها را بهدرستی مدیریت کنند، زیرا ریسهای که قفل
را در اختیار دارد، در نهایت دوباره زمان پردازنده دریافت کرده و میتواند
آن را آزاد کند . با این حال، مسئولیت جلوگیری از رخدادهای رقابتی و بنبستها بر عهدهی برنامهنویس است .
par_unseq: ترکیبی از موازیسازی و برداریسازی
سیاست std::execution::par_unseq یا موازی و بردارگونه، بالاترین سطح از موازیسازی را ممکن میسازد . این خطمشی علاوه بر اجازهی اجرای موازی با چندین ریسه، به کتابخانه اجازه میدهد تا از دستورات بردارگونه (SIMD) نیز برای افزایش سرعت استفاده کند . فراخوانیهای توابع روی عناصر میتوانند بهصورت کاملاً نامرتب در ریسههای نامشخصی اجرا شوند و درون هر ریسه نیز نسبت به یکدیگر غیردنبالهدار هستند، به این معنا که حتی میتوانند با یکدیگر در هم آمیخته شوند . ضمانت پیشرفت در این حالت ضعیفتر از par
است و تنها تضمین میکند که یکی از ریسههایی که قدم برداشته، در نهایت
قدم دیگری بردارد. این موضوع به ریسهها اجازهی ورود به بخشهای بحرانی یا
گرفتن قفل را نمیدهد، زیرا ممکن است ریسهای که قفل را در دست دارد، تا
زمانی که ریسهی دیگری اقدام به گرفتن قفل کند، دوباره زمان پردازنده
دریافت نکند . بههمین دلیل، توابع فراخوانیشده در این سیاست نمیتوانند از عملیاتهای ناایمن برای بردارگونهسازی استفاده کنند، مانند تخصیص حافظه، گرفتن mutex یا استفاده از std::atomic .
مسئولیتهای برنامهنویس در موازیسازی
استفاده از سیاستهای par و par_unseq
مسئولیتهای سنگینی بر عهدهی برنامهنویس میگذارد. جلوگیری از رخدادهای
رقابتی و بنبستها کاملاً بر عهدهی برنامهنویس است و کتابخانه هیچ
تضمینی در این زمینه ارائه نمیدهد . برای مثال، افزودن عناصر به یک std::vector مشترک از درون یک حلقهی موازی for_each، یک رخداد رقابتی است که میتواند به خرابی برنامه منجر شود . همچنین در صورت پرتاب شدن یک استثنا از درون تابع فراخوانیشده در الگوریتم با هر یک از این سیاستها، std::terminate فراخوانی خواهد شد .
قابل ذکر است که اگر پیادهسازی نتواند الگوریتم را بهدلیل کمبود منابع
موازی یا بردارگونه کند، تمام سیاستهای اجرایی استاندارد میتوانند
بهصورت ترتیبی اجرا شوند .
کلیدواژه ها : سیاست اجرایی C++17-std::execution::seq-std::execution::par-std::execution::par_unseq-الگوریتمهای موازی C++17-هدر execution در C++-موازیسازی در STL-برداریسازی C++17-مقایسه seq par par_unseq-ضمانت پیشرفت موازی-مدیریت ریسه در C++17-پرفورمنس الگوریتمهای موازی-نکات std::execution-آموزش سیاستهای اجرایی C++17-محدودیتهای par_unseq-مسئولیت برنامهنویس در موازیسازی-اجرای همزمان الگوریتمها