پردازش موازی (Parallel Computing) در متلب

پردازش موازی (Parallel Computing) در متلب :

نرم افزار متلب، این امکان را به وجود آورده است که در کامپیوترهای دارای پردازنده (CPU) چند هسته ای، از پردازش موازی (Parallel Computing) استفاده نماییم. در این مبحث آموزشی، قصد داریم که نحوه نوشتن کدهای متلب، برای پردازش موازی کدها را شرح بدهیم.

اگر بخواهیم کدهای متلب مورد نظرمان، به صورت موازی پردازش شوند (Parallel Computing)، باید شکل کلی کدها به صورت زیر باشد :

 

matlabpool(‘open’,2);
% your code
matlabpool(‘close’);

که در آن، به جای عبارت % your code ، باید کدهای متلب مورد نظر خود را بنویسید. به عدد ۲ که درون پرانتز اولین دستور matlabpool نوشته شده است، دقت کنید. این عدد، تعداد هسته هایی (cores) را مشخص کرده است که قصد دارید توسط آنها، کدها به صورت موازی پردازش شوند. بنابراین مثلا اگر کامپیوتر شما دارای پردازنده (CPU) چهار هسته ای است، شما باید، حداکثر، عدد ۴ را به جای آن بنویسید.

همچنین توجه داشته باشید که شما می توانید تعیین کنید که همه کدها یا یک یا چند بخش دلخواه از کدهای برنامه متلب خود را، به صورت موازی، پردازش نمایید، بنابراین تنها کافی است که آن بخش های مورد نظرتان از کدها را در میان دو عبارتی که در بالا به کار بردیم، قرار بدهید.

به مثال زیر توجه کنید :

مثال :

در ابتدا کدی را به صورت معمولی (بدون پردازش موازی) می نویسیم و سپس همان را با پردازش موازی اجرا می کنیم و همچنین زمان اجرای دستورات را هم با دو دستور tic و toc محاسبه می کنیم تا ببینیم که پردازش موازی، تا چه حد در سریعتر اجرا شدن کدهای برنامه، تاثیر دارد.

برنامه ای شامل دو حلقه for متوالی می نویسیم، البته به صورت معمولی و بدون استفاده از پردازش موازی :

 

clear all
close all
clc

tic
x=0;
for nn=1:1000
x=nn^5;
end
x

y=0;
for mm=1:1000
y=mm^5;
end
y
toc

نتیجه :

 

x =

۱٫۰۰۰۰e+015

y =

۱٫۰۰۰۰e+015

Elapsed time is 0.005328 seconds.

مشاهده می کنید که زمان پردازش دو حلقه for ، برابر ۰٫۰۰۵۳۲۸ ثانیه بوده است.

حالا این بار، از پردازش موازی استفاده می کنیم تا ببینیم تا چه حد، زمان پردازش کدها کاهش می یابد (البته شروع و پایان خود پردازش موازی، چند ثانیه ای طول می کشد، ولی این میزان، برای برنامه های بزرگ که مد نظر اکثر کاربران است، بسیار ناچیز می باشد) :

 

clear all
close all
clc

matlabpool(‘open’,2); % start of Parallel Computing
tic

x=0;
for nn=1:1000
x=nn^5;
end
x

y=0;
for mm=1:1000
y=mm^5;
end
y

toc
matlabpool(‘close’); % end of Parallel Computing

نتیجه :

 

Starting matlabpool using the ‘local’ configuration connected to 2 labs.

x =

۱٫۰۰۰۰e+015

y =

۱٫۰۰۰۰e+015

Elapsed time is 0.002837 seconds.
Sending a stop signal to all the labs stopped.

این بار، زمان پردازش دو حلقه for ، برابر با ۰٫۰۰۲۸۳۷ ثانیه شده است که از عدد قبلی (پردازش معمولی، پردازش سری)، کمتر است (تقریبا نصف).

نکته :

دقت کنید که در مثال بالا، تنها دو حلقه for ، به طور موازی با هم اجرا می شوند، اما کدهای درون هر حلقه for ، به طور موازی اجرا نخواهند شد (منظور این است که مثلا اگر حلقه for دارای پارامتری از ۱ تا ۱۰۰۰ است، این ۱۰۰۰ بار اجرا شدن حلقه، به صورت موازی نخواهد بود، بلکه به صورت پشت سرهم و سری است).

اما شاید بخواهیم که دستورات درون حلقه for نیز به صورت موازی اجرا گردند، برای این منظور، دستور parfor در متلب در نظر گرفته شده است. دقت کنید که برای استفاده از این دستور، باید هر گام (مرحله) از اجرای حلقه، از دیگر گام ها (مراحل)، مستقل باشد. مثلا اگر در همان مثال قبل، دستورات for را به parfor تبدیل کنیم، جوابی غیر منتظره خواهیم گرفت، زیرا در هر گام (مرحله)، مقدار جدیدی برای متغیر x یا y در نظر گرفته می شود، بنابراین یک متغیر، در گام های مختلف اجرای حلقه، مقادیر متفاوت خواهد داشت، بنابراین گام ها از هم مستقل نیستند. پس نباید برای آن، پردازش موازی به کار ببریم.

برای درک بهتر این موضوع، به مثال زیر توجه کنید :

مثال :

در همان مثال قبل، به جای دستور for ، از دستور parfor استفاده می کنیم :

 

clear all
close all
clc

matlabpool(‘open’,2); % start of Parallel Computing
tic

x=0;
parfor nn=1:1000
x=nn^5;
end
x

y=0;
parfor mm=1:1000
y=mm^5;
end
y

toc
matlabpool(‘close’); % end of Parallel Computing

نتیجه :

 

Starting matlabpool using the ‘local’ configuration connected to 2 labs.

x =

۰

y =

۰

Elapsed time is 0.594762 seconds.
Sending a stop signal to all the labs stopped.

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

اکنون، مثالی را برای parfor می نویسیم که گام های حلقه، از هم مستقل باشند (تعداد گام ها را کمتر کردیم تا نتیجه راحتتر قابل نمایش باشد) :

مثال :

 

clear all
close all
clc

matlabpool(‘open’,2); % start of Parallel Computing
tic

x=zeros(1,10);
parfor nn=1:10
x(1,nn)=nn^5;
end
x

y=zeros(1,10);
parfor mm=1:10
y(1,mm)=mm^5;
end
y

toc
matlabpool(‘close’); % end of Parallel Computing

نتیجه :

 

Starting matlabpool using the ‘local’ configuration connected to 2 labs.

x =

Columns 1 through 7

۱          ۳۲         ۲۴۳        ۱۰۲۴        ۳۱۲۵        ۷۷۷۶       ۱۶۸۰۷

Columns 8 through 10

۳۲۷۶۸       ۵۹۰۴۹      ۱۰۰۰۰۰

y =

Columns 1 through 7

۱          ۳۲         ۲۴۳        ۱۰۲۴        ۳۱۲۵        ۷۷۷۶       ۱۶۸۰۷

Columns 8 through 10

۳۲۷۶۸       ۵۹۰۴۹      ۱۰۰۰۰۰

Elapsed time is 0.676398 seconds.
Sending a stop signal to all the labs stopped.

همین نتیجه را انتظار داشتیم. در این مثال، x و y ، هر کدام دارای ۱۰ عنصر هستند، بنابراین، مثلا اگر دستورات حلقه x ، به صورت موازی اجرا گردند، ۱۰ گام آن، در هم تداخل ندارند، زیرا هر بار، یک عنصر متفاوت از x ، مقداردهی می شود (هیچ دو گامی نیست که در آنها، یک عنصر مشترک، مقداردهی شود).

نکته :

یک سوال مهم، این است که آیا اگر یک تابع را به صورت m-file تعریف کنیم و سپس در یک برنامه متلب، دو بار به آن ارجاع دهیم و برنامه به صورت موازی اجرا شود، آیا عملکرد برنامه صحیح خواهد بود ؟

حال پاسخ این سوال را، با یک مثال، برای شما روشن خواهم کرد :

ابتدا یک تابع با نام my_function می سازیم (تابع به صورت m-file)، که کدهای آن به صورت زیر می باشد :

 

function [x] = my_function(x)
parfor nn=1:10
x(1,nn)=nn^5;
end

این کدها را درون یک m-file نوشته ایم و سپس آن را با نام my_function ذخیره نموده ایم. همچنین فایل این تابع و فایل برنامه ای که خواهیم نوشت، باید هر دو درون یک فولدر قرار داشته باشند (یا مسیر هر دو فایل را به مسیر های نرم افزار متلب اضافه کنیم).

 

سپس یک برنامه متلب می نویسم که در آن، دو بار به تابع my_function ، ارجاع داده شده است :

 

clear all
close all
clc

matlabpool(‘open’,2); % start of Parallel Computing
tic

x=zeros(1,10);
x=my_function(x)

y=zeros(1,10);
y=my_function(y)

toc
matlabpool(‘close’); % end of Parallel Computing

نتیجه :

 

Starting matlabpool using the ‘local’ configuration connected to 2 labs.

x =

Columns 1 through 7

۱          ۳۲         ۲۴۳        ۱۰۲۴        ۳۱۲۵        ۷۷۷۶       ۱۶۸۰۷

Columns 8 through 10

۳۲۷۶۸       ۵۹۰۴۹      ۱۰۰۰۰۰

y =

Columns 1 through 7

۱          ۳۲         ۲۴۳        ۱۰۲۴        ۳۱۲۵        ۷۷۷۶       ۱۶۸۰۷

Columns 8 through 10

۳۲۷۶۸       ۵۹۰۴۹      ۱۰۰۰۰۰

Elapsed time is 0.524047 seconds.
Sending a stop signal to all the labs stopped.

مشاهده می کنید که نتایج صحیح است (البته این پاسخ کلی برای پرسش مورد نظر ما است، اما امکان دارد که کاربر به گونه ای کدهای تابع را بنویسد که تداخل به وجود بیاید، مفهوم تداخل را قبلا برایتان شرح دادم، بنابراین همیشه چک کنید که آیا مراحل عملیات های مختلف، با هم تداخل دارند یا خیر).

بنابراین، پاسخ کلی، هم ((بله)) است و هم ((نه)). یعنی اینکه، اگر کدهای نوشته شده در تابع، تداخل در فرآیند موازی بودن، ایجاد نکنند، مشکلی وجود ندارد (مثل مثال ساده بالا که از کدهای تابع، معلوم است که تداخل ایجاد نمی کند) و اگر کدها، تداخل به وجود بیاورند، نتیجه ای غیرمنتظره، به دست می آید. پس همیشه، مستقل بودن پردازش ها را بررسی کنید.

برای آنکه خیالتان راحت شود، ابتدا کدها را به صورت سری اجرا کنید و بعد یک بار به طور موازی و نتیجه ها را مقایسه کنید. نگویید که خوب اینکه شد دو بار اجرا ! ، خود من برای شبیه سازی انتشار امواج الکترومغناطیسی، مجبور بودم که یک برنامه سنگین بنویسم و بعد آن را صدها بار اجرا نمایم و حالت های مختلف ناشی از ورودی های مختلف را مقایسه کنم، بنابراین تنها کافی بود که دو بار برنامه را تست کنم و بعد صدها بار، از برنامه بهینه شده استفاده نمایم، پروژه تعداد زیادی از دانشجویان نیز اینگونه می باشد و در این موارد، این دو بار اجرا کردن، ارزش صرف وقت را دارد.

نظر خود را اینجا بنویسید!

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *