"> برسی عملکرد حلقه for و foreach در سی شارپ

برسی عملکرد حلقه for و foreach در سی شارپ

for و foreach

در این مقاله به مقایسه ی عملکرد for و foreach در سی شارپ میپردازیم.در طی تجربیاتی که داشتم فهمیدم که دو نوع برنامه نویس وجود دارد. اولین دسته کسانی هستند که تنها میخواهند کار را به اتمام برسانند و دسته دوم کسانی هستند که بسیار مایل به نوشتن کد های خوب هستند. در اینجا یک سوال بزرگ پیش می آید. کد های خوب چه هستند؟

for و foreach:

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

پس زمینه

باید با It و کد های اسمبلی آشنا باشید. همچنین بهتر است دانش کافی بر روی عملکرد چارچوب .Net داشته باشید. برخی از دانش های IT نیز موردنیاز است تا بفهمید دقیقا چه اتفاقی در حال رخ دادن است.

استفاده از کد

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

FOR

int[] myInterger = new int[1];
int total = 0;
for(int i = 0; i < myInterger.Length; i++)
{
    total += myInterger[i];
}

 FOREACH

int[] myInterger = new int[1];
int total = 0;
foreach(int i in myInterger) 
{
    total += i;
}

هر دو کد نتیجه ی مشابهی را ارائه میدهند. Foreach در بالای کالکشن به منظور گذشتن از طریق آن استفاده میشود در حالی که for میتواند بر روی هرچیزی با هدف مشابه ارائه شود. نمیخواهم در مورد اینکه کد چکار میکند صحبت کنم. قبل از وارد شدن بیشتر تصور میکنم که همه ی شما با ILDasm آشنایی دارید که کد IL را تولید میکند و همچنین با CorDbg که به صورت عمومی تولید کد JIT Compiled را برعهده دارد.

کد IL تولید شده ی توسط کامپایلر C# به گستره ی مشخصی بهینه سازی میشود. در حالی که بعضی قسمت ها را برای JIT باقی میگذارد. به هر حال این چیز قابل اهمیتی برای ما نیست. پس زمانی که ما درباره ی بهینه سازی صحبت میکنیم دو چیز را باید مد نظر داشته باشید. اول کامپایلر C# و دومی JIT است.

پس به جای نگاه عمیق تر به کد IL به کد ساطع شده توسط JIT نگاه میاندازیم. این کدی است که بر روی ماشینمان اجرا میشود. من از AMD Athlon 1900+ استفاده میکنم. این کد خیلی به سخت افزار بستگی دارد. بنابراین چیزی که شما از ماشینتان دریافت می کنید ممکن است با سیستم من با توجه به میزان گسترش متفاوت باشد. به هر حال الگوریتم زیاد آن را تغییر نخواهد داد.

در اعلام متغیر ها foreach 5 نوع متغیر دارد ( ۳ عدد صحیح int32 و ۲ آرایه از int32 ) در حالی که for تنها سه تا دارد ( دو عدد صحیح int32 و ۱ آرایه از int32 ). زمانی که وارد حلقه میشوید foreach آرایه ی موجود را در یک آرایه ی دیگر برای اجرای عملیات کپی میکند. در حالی که for به آن اهمیتی نمیدهد.

در اینجا میخواهم وارد تفاوت های دقیق بین کد ها شوم.

FOR

Instruction                           Effect

 

cmp     esi,dword ptr [ebx+4]         i<myInterger.Length

jl     FFFFFFE3

cmp     esi,dword ptr [ebx+4]         i<myInterger.Length

jb     00000009

mov     eax,dword ptr [ebx+esi*4+8]

mov     dword ptr [ebp-0Ch],eax

mov     eax,dword ptr [ebp-0Ch]

add     dword ptr [ebp-8],eax         total += i

inc     esi                           ++i

cmp    esi,dword ptr [ebx+4]         i<myInterger.Length

jl     FFFFFFE3

 

توضیح میدهم که چه اتفاقی در حال رخ دادن است. esi register که نگه دارنده ی مقادیر i و طول آرایه ی myInteger است در دو مرحله مقایسه میشود. اولی زمانی است که شرط چک میشود و اگر حلقه امکان ادامه دادن داشت , مقدار اضافه میشود. برای حلقه در مرحله ی دوم انجام میشود. در داخل حلقه به خوبی بهینه سازی شده است و همانطور که توضیح دادم کار با بهینه سازی عالی انجام میشود.

FOREACH

Instruction                            Effect

cmp     esi,dword ptr [ebx+4]          i<myInterger.Length
jl      FFFFFFE3
cmp     esi,dword ptr [ebx+4]          i<myInterger.Length 
jb      00000009
mov     eax,dword ptr [ebx+esi*4+8] 
mov     dword ptr [ebp-0Ch],eax  
mov     eax,dword ptr [ebp-0Ch]
add     dword ptr [ebp-8],eax          total += i
inc     esi                            ++i
cmp     esi,dword ptr [ebx+4]          i<myInterger.Length
jl      FFFFFFE3

 هر کسی میگوید که این دو شبیه همدیگر هستند. اما ما میخواهیم تفاوت های آن با حلقه ی for را ببینیم. دلیل اصلی برای تفاوت آنها این است که هر دوی آنها به طرز متفاوتی توسط برنامه نویس درک میشوند. الگوریتمی که استفاده میکنند با هم متفاوت است. دو مقایسه ی غیر ضروری , یکی پس از دیگری قرار دارند. هر دو یک کار مشابه را بارها و بارها بدون هیچ دلیلی استفاده میکنند.

cmp                    esi,dword ptr [ebx+4]   
jl                         FFFFFFE3
cmp                    esi,dword ptr [ebx+4]

همچنین از تعدادی move statement غیر ضروری استفاده شده است که ممکن است (نه همیشه , بستگی دارد) باعث کاهش اجرای کد شود. Foreach همه چیز را به عنوان یک کالکشن در نظر میگیرد و مانند یک کالکشن با آن رفتار میکند. این مورد همچنین منجر به به کاهش اجرای کار میشود.

بنابراین به شما توصیه میکنم که اگر هدفتان نوشتن کد هایی با اجرای بالاست از حلقه ی for استفاده کنید. حتی برای کالکشن ها foreach ممکن است مفید بنظر بیاید اما آنقدر هم تاثیرگذار نیست. بنابراین من بالقوه پیشنهاد میکنم که از for به جای foreach در هر مرحله ای استفاده کنید.

نقاط مورد علاقه

در واقع من تحقیقات کوچکی درباره ی مشکلات اجرای کد مخصوصا در زبان های .Net داشتم. زمانی که در حال تست کردن بودم دانستن طرز کار JIT و debug کردن کد تولید شده توسط کامپایلر JIT بسیار امری واجب و ضروری است. مقداری زمان میبرد تا کد را درک کنید.

با تشکر از وقتی که برای مطالعه ی این مقاله گذاشتید

نظرات و پیشنهادات خود را در این باره با من درمیان بگذارید.

  • پسورد: www.mspsoft.com
داریوش فرخی

داریوش فرخی هستم از سال 92 شروع به یادگیری برنامه نویسی و از سال 93 در بخش برنامه نویسی و تولید محتوای سایت mspsoft.com مشغول هستم. فعالیتم نیز بیشتر در زمینه های برنامه نویسی با سی شارپ و asp.net بوده است. اوقات فراغتم را هم غالبا با تماشای فیلم یا بازی های کامپیوتری پر میکنم .

نوشته‌های مرتبط

دیدگاه‌ها

*
*

این سایت از اکیسمت برای کاهش هرزنامه استفاده می کند. بیاموزید که چگونه اطلاعات دیدگاه های شما پردازش می‌شوند.