PDA

View Full Version : زمانگیری اجرای یک برنامه در C



maktoom
سه شنبه 27 دی 1390, 16:34 عصر
سلام
در این تاپیک قصد دارم مطالبی رو د رمورد زمانگیری اجرای کل و یا یک قطعه کد در برنامه بذارم. مطمئنا دوستان واردی در این مطلب حضور دارند که می تونند مطلب رو پربار کنند.
اصولا زمانگیری برای تعیین کارایی یک الگوریتم بکار میره. با داشتن پیچیدگی یک الگوریتم که از چه درجه ایه میشه این مقدار رو با محاسبه t() اون الگوریتم بدست آورد و این مقدار رو از طریق کار گذاشتن کد زمانگیری در ابتدا و انتهای قطعه کد اصلی برنامه مقایسه کرد.
در این میون چیزی که مهمه سیستمیه که برنامه رو اجرا می کنه. چون تموم برنامه ها از الگوریتم های ترتیبی استفاده نمی کنند. ممکنه بصورت موازی یا توزیع شده و یا چند نخی و ... استفاده بشه.
زمانگیری همچنین می تونه برای اثبات بهبود یک الگوریتم و یا بهبود پیاد سازی یک الگوریتم بکار بره. مثلا در یک پردازش کوچیک با کد زمانگیری ثابت میشه اجرای برنامه بصورت موازی کارایی رو با افت روبرو می کنه در حالیکه اگر ورودی های بسیار بالا داده بشه اثبات می کنه که الگوریتم های موازی و پیاده سازیشون بهتر عمل می کنند.
زمانگیری رو میشه به روش های مختلفی انجام داد. اما برخی از این روش ها بدلیل اینکه با پارامترهایی سروکار دارند که ممکنه حین زمانگیری اتفاقات دیگه ای براشون بیفته(مثه سیستمهای چند پردازنده ای) معمولا از یک روش خاص بیشتر استفاده میشه. که من همون روش رو در اینجا معرفی می کنم.
البته بهتره دوستانی که روش های دیگه رو بغیر این روش می دونن در اینجا بیارن. شاید کسی بخواد به دلایلی دقیقا از اون روش ها استفاده کنه نه این روش.
مشکل اصلی در مقادیر ورودی پایین بوجود میاد. جاییکه زمان اجرای یک قطعه کد ممکنه زیر نیم ثانیه باشه. و قراره نموداری کشیده بشه برای نشون دادن رشد مثلا n^3. یا برج های هانوی که به ناگهان تابع پیچیدگی اون رشد نمایی خواهد داشت. یا اصلا کاربر برنامش رو جوری ننوشته که بشه ورودی هایی به اندازه خیلی زیاد داد تا تاخیر زمانی درش محسوس باشه.

این روش بدین صورت است که ابتدا تعداد کلاک اجرای برنامه رابدست آورده و بر تعداد کلاک در هر ثانیه تقسیم می کنیم. نتیجه با دقت میلی ثانیه خواهد بود.
در این روش ضمن اضافه کردن تابع کتابخانه ای time.h به ابتدای برنامه، قبل از اجرای قطعه کد اصلی برنامه باید این خط را اضافه نمود:
clock_t start = clock();
و در پایان قطعه کد اصلی برنامه :
printf("\nTime elapsed: %f\n", ((double)clock() - start) / CLOCKS_PER_SEC);

لازم به ذکر دو نکته در استفاده از این قطعه کد است:
اول اینکه اگر قسمت اول کد از ابتدای برنامه استفاده گردد زمانگیری دقیق نبوده و یا اشتباه خواهد بود. بدین صورت که زمان اضافی شامل مکث کاربر جهت ورودی داده را نیز ثبت خواهد کرد، که این مورد نظر نیست.
دوم اینکه اگر در برنامه خواهان دوبار زمانگیری در یک برنامه باشیم، کافیست نام متغیر زمانگیری را بدین صورت برای بار دوم به بعد تغییر دهیم:
clock_t start1 = clock();
و در چاپ نیز بدین صورت با همان نام ایجاد شده:
printf("\nTime elapsed: %f\n", ((double)clock() – start1) / CLOCKS_PER_SEC);
و همینطور الی آخر برای دیگر زمانگیری ها.

تسهیل عملیات زمانگیری:


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

#include <stdio.h>
#include <conio.h>

int main()
{
int n, x;
printf( "How many disks? " );
scanf( "%d", &n );
printf("\n");
for (x=1; x < (1 << n); x++)
printf( "move from tower %i to tower %i.\n",(x&x-1)%3, ((x|x-1)+1)%3 );

getch();
return 0;
}


می خواهیم به ازای مقادیر 14و 15و 16 و17 و18 و 19 برای n عملیات زمانگیری را در یک بار اجرای برنامه انجام دهیم. با تغییرات گفته شده که در کد برنامه ایجاد می کنیم برنامه بصورت زیر درخواهد آمد:
#include <stdio.h>
#include <conio.h>
#include <time.h>

int main()
{
int n, x;
float t,t1,t2,t3,t4,t5; //متغیرها برای هرکدام از زمانگیری ها

n=14;
clock_t start = clock();
for (x=1; x < (1 << n); x++)
printf( "move from tower %i to tower %i.\n",(x&x-1)%3, ((x|x-1)+1)%3 );
t=(((double)clock() - start) / CLOCKS_PER_SEC); // ذخیره زمان برای ورودی14

n=15;
clock_t start1 = clock();
for (x=1; x < (1 << n); x++)
printf( "move from tower %i to tower %i.\n",(x&x-1)%3, ((x|x-1)+1)%3 );
t1=((double)clock() - start1) / CLOCKS_PER_SEC; // ذخیره زمان برای ورودی15

n=16;
clock_t start2 = clock();
for (x=1; x < (1 << n); x++)
printf( "move from tower %i to tower %i.\n",(x&x-1)%3, ((x|x-1)+1)%3 );
t2=((double)clock() - start2) / CLOCKS_PER_SEC; // ذخیره زمان برای ورودی16

n=17;
clock_t start3 = clock();
for (x=1; x < (1 << n); x++)
printf( "move from tower %i to tower %i.\n",(x&x-1)%3, ((x|x-1)+1)%3 );
t3=((double)clock() - start3) / CLOCKS_PER_SEC; // ذخیره زمان برای ورودی17

n=18;
clock_t start4 = clock();
for (x=1; x < (1 << n); x++)
printf( "move from tower %i to tower %i.\n",(x&x-1)%3, ((x|x-1)+1)%3 );
t4=((double)clock() - start4) / CLOCKS_PER_SEC; // ذخیره زمان برای ورودی18

n=19;
clock_t start5 = clock();
for (x=1; x < (1 << n); x++)
printf( "move from tower %i to tower %i.\n",(x&x-1)%3, ((x|x-1)+1)%3 );
t5=((double)clock() - start5) / CLOCKS_PER_SEC; // ذخیره زمان برای ورودی19

printf("\nTime elapsed: %f\n", t); // چاپ زمان ها بترتیب
printf("\nTime elapsed: %f\n", t1);
printf("\nTime elapsed: %f\n", t2);
printf("\nTime elapsed: %f\n", t3);
printf("\nTime elapsed: %f\n", t4);
printf("\nTime elapsed: %f\n", t5);

getch();
return 0;
}

که نتیجه آن پس از اجرای کامل در خروجی، مطابق شکل زیر خواهد بود:
80993

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

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

برنامه ها با ساختمان داده هایی که ابتدا محاسبات باید کامل انجام شود سپس چاپ در خروجی انجام پذیرد در هنگام دادن ورودی های بزرگ برای چاپ با مکث مواجه می شویم. گاهی این مکث بسیار طولانی می شود. اما برنامه هایی که از پشته و یا تابع بازگشتی بنحوی استفاده می کنند بدین صورت که در هر بار بازگشت چاپ داریم این مکث را ندارند. چون زمان محاسبات بین چاپ ها تقسیم شده و محسوس نیست.

طرح یک سوال:
چرا چاپ؟
چرا در زمانگیری باید چاپ داشته باشیم؟ آیا واقعا چاپ لزومی دارد؟
بسیاری از برنامه ها اصلا چاپ های متوالی ندارند و حتی برای ورودی های بسیار بزرگ هم محاسبات آن بیش از چند دقیقه بطول نخواهد انجامید.
بعنوان مثال در برنامه های برجهای هانوی درصورتی که چاپ متوالی حرکات دیسک را نداشته باشیم براحتی تا حدود 40 دیسک را در مرز دقیقه خواهیم داشت. درحالیکه با چاپ برای 27 دیسک در بهترین حالت(الگوریتم دودویی) 7 ساعت و 20 دقیقه طول می کشد. مشخص است که اکثر زمان مربوط به عمل چاپ کردن است. چاپ کردن چه ارزشی دارد؟ اصلا ارزشی در محاسبات زمانگیری الگوریتم ها دارد؟
برای آزمایش تغییر جزئی در برنامه ایجاد می کنیم. \n های پایان printf ها را برداشته و عبارت توضیحی چاپ را کوتاه می کنیم. نتیجه بسیار تامل برانگیز است. در حالت قبلی برای 22 دیسک زمان در الگوریتم بازگشتی 1، زمان 836.609 ثانیه ثبت شده است. اما در حالتی که تغییرات یاد شده اعمال گشته زمانگیری عدد 629.766 ثانیه را نشان می دهد.
بنظر می رسد بهتر است اگر برنامه قابلیت محاسبه ورودی های بسیار بالا را دارد، یا از چاپ صرفه نظر شود یا عبارت چاپ در نهایت کوتاهی انجام شود تا فقط صحت برنامه آزمایش گردد. در هر حال منحنی ورودی- زمان تغییر محسوسی نخواهد داشت. بدین ترتیب زمان کمتری صرف زمانگیری خواهد شد.

*این مطلب از وبلاگ جناب آقای دکتر کمال میرزایی (www.eboard.blogfa.com)(استاد خوبم) برداشته شده که در قالب پروژه های دانشجویی طراحی الگوریتم ها قرار داده شده است.

loknatesabz
سه شنبه 27 دی 1390, 18:07 عصر
خیلی خوب بود. من واقعا به این توضیحات نیاز داشتم. میشه لطف کنید خود برنامه C رو بذارید تا من بتونم دانلودش کنم چیزی که از اینترنت گرفتم محیطش آبی رنگه و نمیشه برنامه ای که تو nodepad هست رو توش کپی کنم خیلی کار باهاش سخته.
ممنون میشم اگه کسی برنامه C رو بذاره.

maktoom
سه شنبه 27 دی 1390, 23:17 عصر
سلام
شما می تونید از بورلند سی استفاده کنید. نسخه های مختلفی از اون روی سایتهای ایرانی برای دانلود هست.
اگه این قابلیت هایی که گفتید براتون خیلی مهمه بنظر ویژوال استودی رو امتحان کنید بهتره... اما کار باهاش اگه تازه کار هستید یه مقدار سخته.
من وقتی شروع کردم به برنامه نویسی خیلی زود با ویژوال استودیو کار رو پیش بردم. البته اوایلش خیلی لذت بخش خواهد بود.
اما بمرور که به سینتکس های خاص می رسید متوجه تفاوت اونها در ویژوال استودیو با مثلا توربو یا بورلند می شید یه خورده کارو سخت می کنه.(مثل گرافیک)
اگه هم در یک دانشگاه رفت و آمد دارید بنظرم حتما می تونید خیلی راحت بورلند سی رو از مسئول اونجا با یه فلش دریافت کنید و ببرید خونه و نصب کنید.