در این مقاله به توضیح روش های بهبود عملکرد حلقه ها در جاوا می پردازیم و یک اپلیکیشن را در این زمینه ایجاد می کنیم. سپس نکاتی را در مورد آن توضیح می دهیم.
اگر بخواهیم از یک حلقه ی while یا for یا do…while در یک اپلیکیشن استفاده کنیم، می توانیم با انجام کارهای زیر، کارایی این حلقه را بهبود ببخشیم:
- باید اطمینان حاصل کنیم که حلقه ی مورد نظر، حاوی دستورات غیر ضروری نباشد.
- باید ترتیب ارزیابی عملگرهای اتصال کوتاه(short-circuit operators) را در نظر بگیریم.
- باید مقایسه ها را با 0 شروع کنیم.
- باید از ادغام حلقه ها(loop fusion) استفاده کنیم.
اجتناب از دستور های غیر ضروری
ما می توانیم با استفاده نکردن از عملیات ها یا دستورات غیر ضروری، در داخل عبارت تست شونده ی یک حلقه یا در داخل بدنه ی آن، حلقه های کارآمدتری ایجاد کنیم. بعنوان مثال، فرض کنید یک حلقه باید تا زمانیکه x کمتر از جمع دو عدد صحیح a و b است، اجرا شود. این حلقه می تواند به صورت زیر نوشته شود:
while(x < a + b)
//بدنه ی حلقه
این کار را انجام ندهید، انجام این کار، ممکن است برای محاسبه ی مجدد a+b
در هربار تکرار حلقه غیرموثر باشد. اگر این حلقه 1000 بار اجرا شود، آنگاه عبارت a+b به مقدار 1000 بار محاسبه می شود. اما اگر به جای آن، از کد زیر استفاده کنیم، نتیجه یکسان خواهد بود اما محاسبه تنها یک بار انجام می شود:
int sum = a + b;
while(x < sum)
// بدنه ی حلقه
البته، اگر a یا b در بدنه ی حلقه تغییر داده شوند، آنگاه با هربار تکرار حلقه، یک جمع(sum) جدید باید محاسبه شود. اما اگر جمع a و b قبل از اینکه حلقه شروع شود، معین باشد، آنگاه نوشتن کد مورد نظر به روش دوم بسیار موثرتر است.
به طور مشابه، سعی کنید اگر امکان دارد، از فراخوانی متدها در داخل یک حلقه اجتناب کنید. بعنوان مثال، اگر متد getNumberOfEmployees()
همواره در حین اجرای برنامه، تعداد کارمندان را برمی گرداند، آنگاه یک حلقه که به صورت زیر آغاز می شود، ممکن است به صورت غیرضروری این متد را به تعداد زیادی فراخوانی کند:
while(count < getNumberOfEmployees())…
بهتر آن است که این متد را یک بار فراخوانی کنیم و نتیجه را در یک متغیر ذخیره کنیم و از این متغیر در ارزیابی های تکرار شونده استفاده کنیم.
ترتیب ارزیابی عملگرهای اتصال کوتاه(Short-Circuit)
در فصل 5، (یعنی این مقاله) یاد گرفتیم که در عبارت هایی که حاوی AND یا OR هستند، می توانیم از ارزیابی اتصال کوتاه استفاده کنیم. از آنها تنها به تعداد مشخصی استفاده می شود، تا مشخص شود که آیا ارزش کل عبارت true است یا false. وقتی که بخواهیم یک حلقه به تعداد زیادی اجرا شود، بسیار مهم است که تعداد ارزیابی هایی که صورت می گیرد، را در نظر بگیریم.
بعنوان مثال، فرض کنید یک کاربر بتواند تعداد کپی هایی که باید از یک گزارش چاپ شود را با مقداری از 0 تا 15 درخواست کند و ما می خواهیم این ورودی کاربر را قبل از اقدام کردن، اعتبارسنجی کنیم. اگر اعتقاد داشته باشید که کاربران به احتمال زیاد یک مقدار بزرگ یا یک مقدار منفی را وارد می کنند؛ می توانید یک حلقه را شروع کنید که با عبارت زیر از کاربر می خواهد تا مقدار دیگری را وارد کند:
while(requestedNum > LIMIT || requestedNum < 0)…
چون اعتقاد داریم که در کد بالا، ارزش اولین عبارت بولین، نسبت به دومین عبارت، به احتمال زیاد true است، می توانیم تست کردن دومین عبارت را در بیشتر مواقع نادیده گرفته و حذف کنیم. ترتیب این عبارت ها در یک حلقه ی تکی، خیلی مهم نیست اما اگر این حلقه در داخل یک حلقه ی دیگر قرار گیرد، آنگاه تفاوتش این است که تعداد تست ها افزایش می یابد. به همین ترتیب، ترتیب ارزیابی ها در دستورات if، وقتی که این دستورات در داخل یک حلقه قرار می گیرند، اهمیت بیشتری دارد.
انجام مقایسه ها با صفر
مقایسه کردن با 0 سریع تر از مقایسه کردن با هر مقدار دیگری است. بنابراین، اگر مقایسه با 0 برای اپلیکیشن شما امکان پذیر باشد، می توانید کاری کنید که متغیر کنترل حلقه ها به جای مقداری دیگر، از 0 شروع شود تا کارایی حلقه های شما افزایش یابد.
بعنوان مثال، یک حلقه که یک متغیر از 0 تا 100000 دارد، تعداد اجرای آن، با یک حلقه که یک متغیر از 100000 تا 0 دارد، یکسان است؛ اما اجرای حلقه ی دوم کمی سریع تر است. مقایسه ی یک مقدار با 0، به جای دیگر مقادیر، سریع تر است؛ زیرا در یک زبان برنامه نویسی کامپایل شونده، فلگ های شرطی(condition flags)، برای مقایسه فقط یک بار تنظیم می شوند، و مهم نیست که حلقه چند بار اجرا شود. مقایسه ی یک مقدار با 0 سریع تر از مقایسه ی آن با دیگر مقادیر است؛ و فرقی نمی کند که از کدام عملگر استفاده کنیم: بزرگتری، کوچکتری، تساوی یا غیره.
تصویر 6.21 حاوی یک برنامه است که مدت زمان اجرای دو حلقه ی do-nothing تودرتو که کاری انجام نمی دهند را نشان می دهد. حلقه ی do-nothing حلقه ای است که کاری جز چرخیدن انجام نمی دهد. یعنی هیچ بدنه ای برای اجرا ندارد.
قبل از هر حلقه، متد currentTimeMillis()
که متعلق به System است فراخوانی می شود تا زمان فعلی را در واحد میلی ثانیه دریافت کنیم. پس از اینکه هر حلقه ی درونی 100000 بار تکرا می شود، زمان فعلی دوباره دریافت می شود. با تفریق این دو زمان(برای حلقه ی تودرتوی اول و حلقه ی تودرتوی دوم در پایین)، فاصله ی زمانی را به دست می آید.
همان طور که اجرای برنامه در تصویر 6.22 نشان می دهد، اختلاف کوچکی در زمان اجرا، بین این دو حلقه ی تودرتو وجود دارد؛ یعنی حدود یک صدم ثانیه(1/100 ثانیه). در دستگاه های مختلف، این مقدار زمان متفاوت است اما مدت زمان در حلقه ای که از مقایسه با 0 استفاده می کند، هرگز کندتر از دیگری نخواهد بود. این تفاوت، با تکرارهای اضافی یا سطوحِ تودرتوییِ بیشتر، آشکارتر می شود.
اگر حلقه هایی که از متغیر کنترل حلقه استفاده می کنند بعنوان مثال تعداد کاربران را نمایش دهند، آنگاه نیاز دارند این متغیر را افزایش(increment) دهند. اما اگر هدف این حلقه ها فقط شمارش تکرارها باشد، ممکن است بخواهید که در مقایسه ی حلقه از 0 استفاده شود.
(تصویر 6.21 : اپلیکیشن CompareLoops)
برای دانلود این برنامه، اینجا کلیک کنید.
(تصویر 6.22 : اجرای اپلیکیشن CompareLoops)
بکارگیری ادغام حلقه ها(Loop Fusion)
ادغام حلقه(Loop fusion) به فن ترکیب دو حلقه در یکی، گفته می شود. بعنوان مثال، فرض کنید می خواهیم دو متد را هرکدام 100 بار فراخوانی کنیم. برای انجام این کار، یک ثابت به نام TIMES را برابر با 100 تنظیم می کنیم و از کد زیر استفاده می کنیم:
for(int x = 0; x < TIMES; ++x)
method1();
for(int x = 0; x < TIMES; ++x)
method2();
اما، همچنین می توانیم از کد زیر نیز (برای انجام این کار) استفاده کنیم:
for(int x = 0; x < TIMES; ++x)
{
method1();
method2();
}
ادغام حلقه ها، در هر موقعیتی کار نمی کند. گاهی اوقات تمام فعالیت ها برای method1()
باید قبل از اینکه آنها در method2()
بتوانند شروع شوند، پایان یابند؛ اما اگر این دو متد به یکدیگر بستگی نداشته باشند، ادغام این دو حلقه کارایی را بهبود می بخشد.
همان طور که تجربه ی شما در برنامه نویسی بیشتر می شود، راه های دیگری را کشف خواهید کرد تا عملکرد برنامه هایی که می نویسید را بهبود ببخشید. همیشه باید به دنبال راه هایی برای بهبود عملکرد برنامه باشید.اما اگر صرفه جویی در چند میلی ثانیه، باعث می شود درک کدهای شما سخت تر شود، تقریباً همیشه باید به نفع برنامه های کندتر اما خواناتر امتیاز دهید.
دو عبارت صحیح و یک عبارت اشتباه
موضوع: بهبود کارایی حلقه
1. ما می توانیم با اطمینان یافتن از اینکه حلقه ی ما حاوی عملیات غیرضروری در عبارت تست شونده نباشد، کارایی آن را بهبود ببخشیم.
2. ما می توانیم با اطمینان یافتن از اینکه حلقه ی ما حاوی عملیات غیرضروری در بدنه ی خود نیست، کارایی این حلقه را بهبود ببخشیم.
3. وقتی که باید ارزش هردو شرط در تست کردن برای محتمل ترین اتفاق، در ابتدا، true باشد، می توانیم کارایی حلقه را بهبود ببخشیم.
پاسخ:
عبارت شماره 3 اشتباه است. وقتی که با تست کردن برای محتمل ترین اتفاق در ابتدا، دو شرط باید هردو true باشند، می توانیم کارایی حلقه را بهبود ببخشیم. در این روش، تست دوم نیاز دارد کمتر انجام شود.