نگاهی به Parallel Extensions در دات نت 4 بخش اول
۱۳۸۹/۰۳/۲۱ ۲:۲۴ منتشر شده در تاریخ : ۱۳۸۹/۰۳/۲۱ دسته بندی : آموزشی ، برنامه نویسی ، معرفی ، dotNET Framework ، Parallel Extension ، PLinq 1
Parallel Extensions مجموعه ای کلاسها متدها و روشهایی هست که به دات نت 4 اضافه شده و امکان اجرای درخواستها و متد ها را به صورت موازی می دهد. به طور دقیق تر در این روش برنامه می تواند برخی درخواستهای خاص را به صورت موازی در چند هسته CPU به طور همزمان اجرا کند بدون اینکه در روند عادی اجرای برنامه وقفه ای ایجاد شود ویا نیاز به تغییر عمده ای داشته باشد.
شاید بپرسید که Threading همین کار را انجام می دهد و چه نیازی به استفاده از این روش وجود دارد. در پاسخ باید گقت Parallelization (یا همزمان سازی) کاربردی نسبتا متفاوت از کاربرد thread ها دارد، گرچه خود شیوه parallel از thread ها استفاده می کند. در حقیقیت parallel ها برای استفاده سریع تر از thread ها در مقاصد خاص ایجاد شده اند و نه جایگزینی برای آن؛ جایی که تعداد زیادی از thread باعث ایجاد مشکلات مدیریتی می شود.
در تصویر زیر مشاهده میکنید که thread ها در یک سیستم تک هسته ای به صورت ترتیبی اجرا می شود و parallel در یکی سیستم چند هسته ای همزمان اجرا می شود.
(توجه کنید که thread ها هم در سیستم چند هسته ای می توانند در هسته های مختلف می توانند اجرا شوند ولی تضمینی برای اجرای همزمان آنها وجود ندارد و کنترل آن توسط سیستم عامل انجام می شود.)
دو متد Parallel.For و Parallel.ForEach که در فضای نام System.Threading.Tasks قرار دارند برای انجام حلقه های پارالل در نظر گرفته شده اند. نسخه های مختلفی از آنها با توجه به ورودی و خروجی این متد ها در نظر گرفته شده تا همه شرایط را پوشش دهند. اما برای استفاده از لازم نیست که حتما از این توابع استفاده کنید. متد اضافه شونده AsParallel که جزوی از PLINQ برای راحتی کار در نظر گرفته شده است.
همه لیست ها و آرایه هایی که اینترفیس IEnumerable رو پیاده سازی می کنند بسادگی و با استفاده از متد اضافه شوند (extension method) با نام AsParallel امکان استفاده از پارالل ها را دارند. این متد که در مبحث PLINQ کاربرد دارد کار را بسیار ساده کرده و روش استفاده از آن در حد یک فراخوانی ساده کرده است.
در مثال ساده زیر یک حلقه از 0 تا 100 داریم که به صورت پارالل اجرا می شود. در بخش بعدی در مثالی در مورد AsParallel را مشاهده خواهید کرد.
در اینجا Task.CurrentId مقدار id تسک در حال اجرا نشان می دهد. در بخش بعدی در مورد تسک ها خواهم نوشت.
کارایی این روش بستگی زیادی به کاری که می خواهید با آن انجام دهید دارد. همیشه سعی کنید کارهایی را که به زمان زیادی نیاز دارد را به صورت پارالل تبدیل اجرا کنید. برای مثال دانلود فیدهای چندین سایت مختلف برای نوشتن نرم افزار فید خوان، یا جستجو در میان لیست یا آرایه ای طولانی.
دلیل این امر زمانی است که دات نت برای آماده سازی تسک و تقسیم وظایف انجام می دهد. گرچه برای سیستم های سریع فعلی این زمان در حد چند میلی ثانیه است ولی ممکن است در برخی سیستم های قدیمی تر اندکی محسوس تر باشد.
همچنین در هنگام استفاده از parallel ها همیشه در نظر داشته باشید که بهترین نتیجه را زمانی خواهید گرفت که cpu سیستم دارای چند هسته باشد. در غیر این صورت این روش ممکن است اندکی سربار ایجاد کند.
درمثال زیر می خواهیم یک کار زمانبر را دو روش parallel و serial اجرا کنیم و نتایج را بررسی کنیم.
تصویر زیر نتیجه اجرای این برنامه را در لپتابم که cpu اون core 2 due 2.6 دو هسته ای هست رو نشان میده.
تفاوت فاهش هست. 10 ثانیه برای serial و 5 ثانیه برای parallel. مدت زمان اجرا با استفاده از parallel تقریبا نصب شده است. البته این بهترین حالت برای یک سیستم دو هسته ای است چون این تست هیچ فشاری رو روی سیستم وارد نمی کنه.
نکته بسیار مهم: اگه به تصویر بالا دقت کنید در هنگام اجرای تست parallel تسک ها بدون ترتیب و در هم اجرا شده اند. این به این علت است که تسک ها در میان هسته های cpu تقسیم شده اند تا تداخلی در هنگام اجرا ایجاد نشود. نحوه تقسیم را هم دات نت تنظیم می کند که در ایجا و برای cpu دو هسته ای برای یک هسته از اول تا وسط و برای هسته بعدی از وسط تا آخر پردازش شده است.
پس همیشه در استفاده از parallel ها دقت کنید که آیا نیاز دارید که لیست به صورت مرتب بررسی شود یا نه. در صورتی که لازم است لیست به صورت مرتب پارالل شود باید از متد Parallel.For که قبلا اشاره شد استفاده کنید. این متد تظمین می کند که لیست شما مرتب و در عین حال parallel اجرا شود. شیوه رفع این مشکل در PLINQ در ادامه گفته خواهد شد.
دو متد Parallel.For و Parallel.ForEach کلاس ParallelOptions را به عنوان ورودی قبول می کنند که کنترل بیشتری بر تسک ها در اخیار می گذارد. برای اطلاعات بیشتر در مورد ParallelOptions به منابع انتهای مقاله مراجه کنید.
نکته مهم - اشتباه به کار گیری: نکته دیگری که لازم به ذکر است این است که نباید لیست هایی که توسط تابع AsParallel به لیست های پارالل تبدیل شده اند را توسط دستور foreach ساده فراخوانی کرد. برای مشاهده نتیجه دقیق کد زیر را در مثال بالا جایگزین کرده و اجرا کردم نتیجه آن را در تصویر زیر می بینید:
اگر دقت کرده باشید من در مثالهای قبلی از PLINQ استفاده کرده ام که قلب اصلی آن متد AsParallel هست. PLINQ نسخه parallel شده LINQ هست که علاوه بر متد ذکر شده شامل متدهای دیگری برای مدیریت ویژگی parallel آن هم هست. تعدادی از این متد های اصلی در لیست زیر را ذکر می کنم:
استفاده از موارد فوق در پرس و جو ها به سادگی امکان پذیر است:
متد ForAll: کاربرد این متد برای PLINQ در این است که بدون کش کردن و تلفیق کردن(result merging) نتیجه را فورا بر می گرداند. این باعث خواهد شد که نتایج AsOrdered نادیده گرفته بشوند. برای کنترل بیشتر در مورد نحوه کش کردن و تلفیق کردن نتایج از متد WithMergeOptions که ورودی از نوع ParallelMergeOptions می گیرد را استفاده کنید.
بخش دوم این مقاله در مورد Task ها خواهد بود.
موفق باشید.
نگاهی به Parallel Extensions در دات نت 4 بخش اول
شاید بپرسید که Threading همین کار را انجام می دهد و چه نیازی به استفاده از این روش وجود دارد. در پاسخ باید گقت Parallelization (یا همزمان سازی) کاربردی نسبتا متفاوت از کاربرد thread ها دارد، گرچه خود شیوه parallel از thread ها استفاده می کند. در حقیقیت parallel ها برای استفاده سریع تر از thread ها در مقاصد خاص ایجاد شده اند و نه جایگزینی برای آن؛ جایی که تعداد زیادی از thread باعث ایجاد مشکلات مدیریتی می شود.
- فرق بین Threading و Parallel
در تصویر زیر مشاهده میکنید که thread ها در یک سیستم تک هسته ای به صورت ترتیبی اجرا می شود و parallel در یکی سیستم چند هسته ای همزمان اجرا می شود.
(توجه کنید که thread ها هم در سیستم چند هسته ای می توانند در هسته های مختلف می توانند اجرا شوند ولی تضمینی برای اجرای همزمان آنها وجود ندارد و کنترل آن توسط سیستم عامل انجام می شود.)
- Parallel Loops
دو متد Parallel.For و Parallel.ForEach که در فضای نام System.Threading.Tasks قرار دارند برای انجام حلقه های پارالل در نظر گرفته شده اند. نسخه های مختلفی از آنها با توجه به ورودی و خروجی این متد ها در نظر گرفته شده تا همه شرایط را پوشش دهند. اما برای استفاده از لازم نیست که حتما از این توابع استفاده کنید. متد اضافه شونده AsParallel که جزوی از PLINQ برای راحتی کار در نظر گرفته شده است.
همه لیست ها و آرایه هایی که اینترفیس IEnumerable رو پیاده سازی می کنند بسادگی و با استفاده از متد اضافه شوند (extension method) با نام AsParallel امکان استفاده از پارالل ها را دارند. این متد که در مبحث PLINQ کاربرد دارد کار را بسیار ساده کرده و روش استفاده از آن در حد یک فراخوانی ساده کرده است.
در مثال ساده زیر یک حلقه از 0 تا 100 داریم که به صورت پارالل اجرا می شود. در بخش بعدی در مثالی در مورد AsParallel را مشاهده خواهید کرد.
Parallel.For(0, 100, index => { Console.WriteLine("Id:{0} Num:{1} - ", Task.CurrentId, index); });
در اینجا Task.CurrentId مقدار id تسک در حال اجرا نشان می دهد. در بخش بعدی در مورد تسک ها خواهم نوشت.
- کارایی Parallel سازی
کارایی این روش بستگی زیادی به کاری که می خواهید با آن انجام دهید دارد. همیشه سعی کنید کارهایی را که به زمان زیادی نیاز دارد را به صورت پارالل تبدیل اجرا کنید. برای مثال دانلود فیدهای چندین سایت مختلف برای نوشتن نرم افزار فید خوان، یا جستجو در میان لیست یا آرایه ای طولانی.
دلیل این امر زمانی است که دات نت برای آماده سازی تسک و تقسیم وظایف انجام می دهد. گرچه برای سیستم های سریع فعلی این زمان در حد چند میلی ثانیه است ولی ممکن است در برخی سیستم های قدیمی تر اندکی محسوس تر باشد.
همچنین در هنگام استفاده از parallel ها همیشه در نظر داشته باشید که بهترین نتیجه را زمانی خواهید گرفت که cpu سیستم دارای چند هسته باشد. در غیر این صورت این روش ممکن است اندکی سربار ایجاد کند.
درمثال زیر می خواهیم یک کار زمانبر را دو روش parallel و serial اجرا کنیم و نتایج را بررسی کنیم.
class LongTask { public int ID { get; set; } public void DoIt() { Console.WriteLine("The long task " + ID); System.Threading.Thread.Sleep(500); } } static void Main(string[] args) { // The list List<LongTask> taskList = new List<LongTask>(); // The items for (int i = 0; i < 20; i++) { taskList.Add(new LongTask { ID = i }); } // Serial Console.WriteLine("Starting serial test"); var swatch = Stopwatch.StartNew(); foreach (var item in taskList) { // the long task item.DoIt(); } swatch.Stop(); // parallel Console.WriteLine("Starting parallel test"); var pwatch = Stopwatch.StartNew(); taskList.AsParallel().ForAll(x => { x.DoIt(); }); pwatch.Stop(); Console.WriteLine(); Console.WriteLine("Parallel programming demo, salarblog.wordpress.com"); Console.WriteLine("Serial task finished in {0} seconds", swatch.Elapsed.TotalSeconds); Console.WriteLine("Parallel task finished in {0} seconds", pwatch.Elapsed.TotalSeconds); // wait Console.ReadKey(); }
تصویر زیر نتیجه اجرای این برنامه را در لپتابم که cpu اون core 2 due 2.6 دو هسته ای هست رو نشان میده.
تفاوت فاهش هست. 10 ثانیه برای serial و 5 ثانیه برای parallel. مدت زمان اجرا با استفاده از parallel تقریبا نصب شده است. البته این بهترین حالت برای یک سیستم دو هسته ای است چون این تست هیچ فشاری رو روی سیستم وارد نمی کنه.
نکته بسیار مهم: اگه به تصویر بالا دقت کنید در هنگام اجرای تست parallel تسک ها بدون ترتیب و در هم اجرا شده اند. این به این علت است که تسک ها در میان هسته های cpu تقسیم شده اند تا تداخلی در هنگام اجرا ایجاد نشود. نحوه تقسیم را هم دات نت تنظیم می کند که در ایجا و برای cpu دو هسته ای برای یک هسته از اول تا وسط و برای هسته بعدی از وسط تا آخر پردازش شده است.
پس همیشه در استفاده از parallel ها دقت کنید که آیا نیاز دارید که لیست به صورت مرتب بررسی شود یا نه. در صورتی که لازم است لیست به صورت مرتب پارالل شود باید از متد Parallel.For که قبلا اشاره شد استفاده کنید. این متد تظمین می کند که لیست شما مرتب و در عین حال parallel اجرا شود. شیوه رفع این مشکل در PLINQ در ادامه گفته خواهد شد.
دو متد Parallel.For و Parallel.ForEach کلاس ParallelOptions را به عنوان ورودی قبول می کنند که کنترل بیشتری بر تسک ها در اخیار می گذارد. برای اطلاعات بیشتر در مورد ParallelOptions به منابع انتهای مقاله مراجه کنید.
نکته مهم - اشتباه به کار گیری: نکته دیگری که لازم به ذکر است این است که نباید لیست هایی که توسط تابع AsParallel به لیست های پارالل تبدیل شده اند را توسط دستور foreach ساده فراخوانی کرد. برای مشاهده نتیجه دقیق کد زیر را در مثال بالا جایگزین کرده و اجرا کردم نتیجه آن را در تصویر زیر می بینید:
foreach (var item in taskList.AsParallel())
{
// the long task
item.DoIt();
}
- PLINQ - Parallel LINQ
اگر دقت کرده باشید من در مثالهای قبلی از PLINQ استفاده کرده ام که قلب اصلی آن متد AsParallel هست. PLINQ نسخه parallel شده LINQ هست که علاوه بر متد ذکر شده شامل متدهای دیگری برای مدیریت ویژگی parallel آن هم هست. تعدادی از این متد های اصلی در لیست زیر را ذکر می کنم:
- AsParallel: همانظور که ذکر شد یک پرس و جوی LINQ را به یک پر جوی PLINQ تبدیل می کند. به طور دقیق تر یک شیء ParallelQuery را از روی IEnumerable ایجاد می کند. ParallelQuery نماینده یک پرس و جوی PLINQ می باشد.
- AsSequential: عکس کار متد AsParallel را انجام می دهد، یعنی یکی پرس و جوی پارالل را به یک پرس و جوی معمولی linq تبدیل می کند.
- AsOrdered: پرس و جوی پارالل را مجبور می کند که به ترتیب اجرا شود. این دقیقا همان راه حل مشکلی است که در بالا برای درهم اجرا شدن تسک های parallel ذکر شد. این راه حل برای نسخه PLINQ کاربرد دارد.
- AsUnordered: عکس متد بالا را انجام می دهد.
استفاده از موارد فوق در پرس و جو ها به سادگی امکان پذیر است:
var resultSet = from t in taskList.AsParallel()
select t;
متد ForAll: کاربرد این متد برای PLINQ در این است که بدون کش کردن و تلفیق کردن(result merging) نتیجه را فورا بر می گرداند. این باعث خواهد شد که نتایج AsOrdered نادیده گرفته بشوند. برای کنترل بیشتر در مورد نحوه کش کردن و تلفیق کردن نتایج از متد WithMergeOptions که ورودی از نوع ParallelMergeOptions می گیرد را استفاده کنید.
بخش دوم این مقاله در مورد Task ها خواهد بود.
موفق باشید.
- منابع و لینک های مفید:
APress - Pro .NET 4 Parallel Programming in C#
APress - Introducing .NET 4.0
O'Reilly - C# 4.0 IN A NUTSHELL
Threading/Concurrency vs Parallelism
How to: Control Ordering in a PLINQ Query
Why is ParallelQuery.Where not working when converting to Observable?
APress - Introducing .NET 4.0
O'Reilly - C# 4.0 IN A NUTSHELL
Threading/Concurrency vs Parallelism
How to: Control Ordering in a PLINQ Query
Why is ParallelQuery.Where not working when converting to Observable?
نگاهی به Parallel Extensions در دات نت 4 بخش اول
خیلی جامع و جالب بود.
پاسخحذفممنون ;)