نگاهی به Parallel Extensions در دات نت 4 بخش اول

۱۳۸۹/۰۳/۲۱ ۲:۲۴ Salar Khalilzadeh https://plus.google.com/105397214522932500988 منتشر شده در تاریخ : ۱۳۸۹/۰۳/۲۱ دسته بندی : ، ، ، ، ، 1

Parallel Extensions مجموعه ای کلاسها متدها و روشهایی هست که به دات نت 4 اضافه شده و امکان اجرای درخواستها و متد ها را به صورت موازی می دهد. به طور دقیق تر در این روش برنامه می تواند برخی درخواستهای خاص را به صورت موازی در چند هسته CPU به طور همزمان اجرا کند بدون اینکه در روند عادی اجرای برنامه وقفه ای ایجاد شود ویا نیاز به تغییر عمده ای داشته باشد.

شاید بپرسید که Threading همین کار را انجام می دهد و چه نیازی به استفاده از این روش وجود دارد. در پاسخ باید گقت Parallelization (یا همزمان سازی) کاربردی نسبتا متفاوت از کاربرد thread ها دارد، گرچه خود شیوه parallel از thread ها استفاده می کند. در حقیقیت parallel ها برای استفاده سریع تر از thread ها در مقاصد خاص ایجاد شده اند و نه جایگزینی برای آن؛ جایی که تعداد زیادی از thread باعث ایجاد مشکلات مدیریتی می شود.
  • فرق بین Threading و Parallel
به طور خلاصه می توان چند thread را در یک cpu تک هسته ای اجرا کرد ولی parallel سازی فقط در cpu های چند هسته ای اتفاق خواهد افتاد. در هنگام استفاده از parallel ها دات نت به طور خودکار اقدام به شناسایی سیستم برای دارا بودن cpu چند هسته ای می کند، در صورتی سیستم دارای چنین قابلیتی باشد دات نت اقدامات بعدی را برای اجرای پروسه به صورت parallel انجام خواهد داد. این یکی از مزایای این روش هست که تضمین می کند کد برنامه در هر سیستمی قابل اجرا خواهد بود.

در تصویر زیر مشاهده میکنید که thread ها  در یک سیستم تک هسته ای به صورت ترتیبی اجرا می شود و parallel در یکی سیستم چند هسته ای همزمان اجرا می شود.
(توجه کنید که thread ها هم در سیستم چند هسته ای می توانند در هسته های مختلف می توانند اجرا شوند ولی تضمینی برای اجرای همزمان آنها وجود ندارد و کنترل آن توسط سیستم عامل انجام می شود.)

Serial vs. Parallel

  • 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 دو هسته ای هست رو نشان میده.

Parallel Performance Test


تفاوت فاهش هست. 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();
}

Wrong Parallel Usage

همانطور که مشاهده می کنید نه تنها استفاده از ناصحیح از پارالل باعث کاهش زمان نشده بلکه چند میلی ثانیه این زمان بیشتر نیز شده است. دلیل این است که با استفاده از متد AsParallel یک لیست parallel بدست می آوریم ولی دستور foreach ساده این لیست را به طور ساده و serial اجرا می کند و هیچ عمل پارالل سازی اتفاق نمی افتد.
  • 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 ها خواهد بود.

موفق باشید.
  • منابع و لینک های مفید:


نگاهی به Parallel Extensions در دات نت 4 بخش اول

 

یک بازخورد برای “نگاهی به Parallel Extensions در دات نت 4 بخش اول”