یادداشتهای یک برنامه نویس

بیان تجربیات و دیدگاه های یک برنامه نویس در مورد نرم افزار , طراحی و تولید برنامه با استفاده از تکنولوژی های مایکروسافت

یادداشتهای یک برنامه نویس

بیان تجربیات و دیدگاه های یک برنامه نویس در مورد نرم افزار , طراحی و تولید برنامه با استفاده از تکنولوژی های مایکروسافت

۳ مطلب در اسفند ۱۳۹۲ ثبت شده است

يكشنبه, ۱۱ اسفند ۱۳۹۲، ۰۵:۵۶ ب.ظ

استفاده یا عدم استفاده از Generic


     در این برنامه ابتدا یک کلاس بسیار ساده برای بررسی نحوه انجام پاکسازی حافظه و به دست آوردن معیار مناسبی جهت اندازه گیری میزان کارآیی استفاده از تکنیک Generic ایجاد شده است.
بررسی نتایج حاصل از این برنامه نشان میدهد که استفاده از Generic بویژه در انواع ValueType از نظر مصرف منابع، بسیار مقرون به صرفه تر بوده و سرعت انجام عملیات به مراتب بالاتر خواهد بود. در انواع Reference Type نیز این افزایش کارآیی وجود دارد اما به اندازه مورد قبلی محسوس نیست. خروجی برنامه ، زمان صرف شده برای درج 10 میلیون عنصر از یک نوع Value Type (در مثال ما این نوع int انتخاب شده است) به ترتیب با استفاده از Generic و بدون آن ، و همچنین همین عملیات مشابه برای یک نوع Reference Type (در مثال این نوع string انتخاب شده است) را با استفاده از کلاس StopWatch و تعداد تلاشهای CLR برای پاکسازی (Garbage Collector) با استفاده از متد استاتیک کلاس GC، در طول این پروسه ، نشان داده شده است. با تغییر در مقدار Max_Loop_Count ، مثلا بیشتر کردن آن امکان بروز Out Of Memory Exception وجود دارد و به دلیل سادگی و خلاصه بودن کد، در برنامه تمهیداتی برای کنترل آن در نظر گرفته نشده است.
 

using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;

namespace GenericOrNotGeneric
{

class Program
{

const Int32 Max_Loop_Count = 10000000;
static void Main(string[] args)
{

ValueTypePerformanceTest();
ReferenceTypePerformanceTest();
Console.WriteLine("Now in the end you decide. ");
Console.WriteLine("Please press <Enter> to exit...");
Console.ReadLine();
}

private static void ValueTypePerformanceTest()
{

using (new OperationTimer("List<Int32>"))
{
List<Int32> l = new List<Int32>();
for (Int32 n = 0; n < Max_Loop_Count; n++)
{
l.Add(n);
Int32 x = l[n];
}
l = null;
}
using (new OperationTimer("ArrayList of Int32"))
{
ArrayList a = new ArrayList();
for (Int32 n = 0; n < Max_Loop_Count; n++)
{
a.Add(n);
Int32 x = (Int32)a[n];
}
a = null;
}
}
private static void ReferenceTypePerformanceTest()
{

using (new OperationTimer("List<String>"))
{
List<String> l = new List<String>();
for (Int32 n = 0; n < Max_Loop_Count; n++)
{
l.Add("X");
String x = l[n];
}
l = null;
}
using (new OperationTimer("ArrayList of String"))
{
ArrayList a = new ArrayList();
for (Int32 n = 0; n < Max_Loop_Count; n++)
{
a.Add("X");
String x = (String)a[n];
}
a = null;
}
}
}

internal sealed class OperationTimer : IDisposable
{
private Stopwatch m_stopwatch;
private String m_text;
private Int32 m_collectionCount;
public OperationTimer(String text)
{
PrepareForOperation();
m_text = text;
m_collectionCount = GC.CollectionCount(0);
m_stopwatch = Stopwatch.StartNew();
}
public void Dispose()
{
Console.WriteLine("{0} (GCs={1,3}) {2}", (m_stopwatch.Elapsed),
GC.CollectionCount(0) - m_collectionCount, m_text);
}
private static void PrepareForOperation()
{
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
}
}
}

 

۰ نظر موافقین ۰ مخالفین ۰ ۱۱ اسفند ۹۲ ، ۱۷:۵۶
مهران حسین نیا
شنبه, ۳ اسفند ۱۳۹۲، ۰۶:۱۳ ب.ظ

یک توضیح ضروری

مطلبی که در پست قبلی شروع کرده ام به ظاهر مطلب ساده ای میباشد. در آغاز مثل همیش سعی کردم که از چند منبع معتبر استفاده کنم که تا حد امکان موضوع اصلی حفظ شده و به بیراهه کشیده نشود. شاید بتوان به طور خلاصه چنین عنوان کرد که موضوع اصلی مورد نظر من Types و مباحث مربوط به آن در سی شارپ بود.
اما انتخاب یک حد و مرز مشخص برای این بحث بسیار مشکل تر از آن است که در ابتدا به نظر میرسید. هدف اصلی من نوع متفاوت تلقی کامپایلر از Reference Typeها و Value Typeها ، تفاوت نوع تخصیص حافظه و به دست آوردن یک معیار صحیح در انتخاب هر یک از این دو نوع در سناریو های رایج برنامه نویسی می باشد.
اما هر چه بیشتر در این باره فکر میکنم در می یابم که مطرح کردن این بحث بدون اشاره به مطالبی مثل انواع Nullableها، Boxing و UnBoxing ، توصیف دقیق و کافی در مورد Heap و Stack ، اشاره گرها و Pointer ها و طرح مثالهایی که مرتبط با موضوع باشند، ضروری ولی  بسیار دشوار است.
در این راه مخصوصا استفاده از Utility های WinDump و WinDBG که با گزارشهای مفصل و نشان دادن مکان های دقیق حافظه درک این مفاهیم را آسان تر خواهند نمود، اجتناب ناپذیر هستند.
این توضیح از آن جهت به نظرم ضروری بود که به هر حال معمولا در همه کتابهای آموزش برنامه نویسی سی شارپ، مخصوصا در ابتدا مباحث مربوط به Types ، انواع مختلف آنها و ظرفیت های هر یک در قالب یک جدول و توضیحات کامل ارائه میشود و تلاش من در این باره با همه بضاعت اندکی که دارم به این خاطر است که این مبحث با دید عمیق تری بررسی شده و نتایج با مثالهای گویا و روشن ارائه شوند.
یقینا در راه رسیدن به این هدف سعی خواهم کرد که از تکرار مکرارات پرهیز کرده و با استفاده از منابع معتبر مطالب مورد نظر را تا حد امکان موشکافی کرده و بررسی کنم. از توجه همه دوستان و همکاران محترم که مشوق اصلی من در این کار می باشد سپاسگذارم.
 

۰ نظر موافقین ۰ مخالفین ۰ ۰۳ اسفند ۹۲ ، ۱۸:۱۳
مهران حسین نیا
جمعه, ۲ اسفند ۱۳۹۲، ۰۹:۴۸ ب.ظ

نگاهی به مباحث پایه (قسمت اول)

     یکی از مباحث پایه در همه زبان های برنامه نویسی مبحث Types و مطالعه نوع پیاده سازی هر زبان برای ارائه انواع پایه  و در نهایت ترکیب آنها برای دستیابی به انواع پیچیده تر مثل Structure ها و Classهاست.
در C# از دیدگاه نوع تخصیص حافظه دو گروه Type یا نوع مختلف وجود دارد که Value Types و Reference Types نامیده میشوند.

انواع Value Types

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

من برای نشان دادن این موضوع از یک مثال خیلی ساده استفاده کرده ام که از آنجاییکه نوع استفاده من از Pointerها از دیدگاه کامپایلر Safe و ایمن نیست اجبارا باید آن را در داخل یک بلاک unsafe قرار داده و ضمنا به کامپایلر گوشزد کنم که این کد را unsafe کامپایل نماید.

 

Namespace TypesInCSharp

{

    class Program

    {

        static void Main(string[] args)

        {

            unsafe

            {

                int j = 10;

                int* ip = &j;

                Console.WriteLine(“The value of j which means {0} stored in {1} location.”, j, (int)ip);

                Console.WriteLine(“Please press <Enter> key to exit…”);

                Console.ReadLine();

            }

        }

    }

}

 

 

در سیستم من پس از اجرا خروجی چیزی شبیه به زیر خواهد بود:

 

The value of j which means 10 stored in 92465428

Please press………………………………………..

 

تفسیر ساده این مثال به این صورت است که با تعریف متغیر j فضایی در حافظه تخصیص (Allocate) داده میشود که آدرس این ناحیه از حافظه  92465428 می باشد. در داخل این فضا عدد 4 قرار گرفته است.

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

 

namespace TypesInCSharp

{

    class Program

    {

        static void Main(string[] args)

        {

            unsafe

            {

                int j = 10;

                Console.WriteLine("The value of p before method call is {0} ", j);

                TryToChangeValue(j);

                Console.WriteLine("The value of p after method still unchanged and is {0} ", j);

                Console.WriteLine("Please press <Enter> key to exit...");

                Console.ReadLine();

            }

        }

        public static void TryToChangeValue(int p)

        {

            p = 20;

            Console.WriteLine("The value of p inside method is {0} ", p);

        }

    }

}

 

متد TryToChangeValue سعی بیهوده ای میکند که مقدار پارامتر وارد شده را تغییر دهد. J  قبل از ورود به داخل متد دارای مقدار 10 میباشد . در ادامه j به عنوان پارامتر وارد متد میشود. در داخل متد چنانچه خروجی برنامه نیز نشان میدهد با اینکه پارامتر ورودی تغییر داده شده اما مقدار آن بدون تغییر باقی می ماند. زیرا که متد مذکور فقط به یک کپی از متغیر j دسترسی دارد و توانایی تغییر در متغیر اصلی J را ندارد.

 

The value of p before method call is 10

The value of p inside method is 20

The value of p after method still unchanged and is 10

Please press <Enter> key to exit...

 

  اما در مثال زیر:

 

namespace TypesInCSharp

{

    class SimpleClass

    {

        public int i ;

    }

    class Program

    {

        static void Main(string[] args)

        {

            unsafe

            {

                SimpleClass simpleClass = new SimpleClass();

                simpleClass.i = 10;

                Console.WriteLine("The value of simpleClass.i before method call is {0} ", simpleClass.i);

                TryToChangeValue(simpleClass);

                Console.WriteLine("The value of simpleClass.i after method changed and is {0} ", simpleClass.i);

                Console.WriteLine("Please press <Enter> key to exit...");

                Console.ReadLine();

            }

        }

        public static void TryToChangeValue(SimpleClass simpleclass)

        {

            simpleclass.i = 20;

            Console.WriteLine("The value of simpleclass.i inside method is {0} ", simpleclass.i);

        }

    }

}

 

که خروجی زیر را ایجاد میکند ماجرا به گونه دیگریست

 

The value of simpleClass.i before method call is 10

The value of simpleclass.i inside method is 20

The value of simpleClass.i after method changed and is 20

Please press <Enter> key to exit...

 

                    ادامه دارد ...

۰ نظر موافقین ۰ مخالفین ۰ ۰۲ اسفند ۹۲ ، ۲۱:۴۸
مهران حسین نیا