mostafa vakili
mostafa vakili
خواندن ۵ دقیقه·۵ سال پیش

کلاس های Generic در خدمت پرفورمنس

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

در ادامه بهبود فریمورکی که قبلا در موردش صحبت کردم رسیدم به جاییی که دیدم برای اضافه کردن یه فرم Crud کلی باید کلاس اضافه کنیم به پروژه, که شامل کلاس های مربوط به مدل, سرویس, repository, ایجاد گرید, ایجاد کلاس هایی مخصوص combobox(یه چیزی مثل select2 که کاملا سفارشی توی فریم ورک ایجاد شده), بعد از کلی کلنجار رفتن با این مسئه که چرا اصلا!!! باید همچین کارهایی کرد اونم جایی که واقعا خیلی از متدها اضافی هستش و نیازی به این کار نیستش, اومدم یه تغییراتی توی ساختار پروژه ایجاد کردم که وقتی میخواستیم فقط یه گرید, فرم سرچ روی گرید و عملیات های حذف و اضافه و ویرایش داشته باشیم به جای اینکه مجبور بشم 15 تا کلاس بنویسیم فقط یه کلاس مدل داشته باشیم با controller مربوطه و تمام(کاری ندارم که این کار رو چطوری انجام دادم چون خارج از بحث این مطلب هستش).

خب کار رو شروع کردم و برای این که بتونم به چیزی که میخوام روی این ساختار کاملا Generic برسم این بود که باید سرویس هایی که به صورت خودکار در زمان اجرا ایجاد میشدن میفهمیدن که مدل اصلی کدومه, مدل مربوط به کنترل های جستجو کدومه و ....

برای این که پاراگراف بالا رو درک کنید بزاریم با یه مثال ادامه بدم, فرض کنید به ما گفتن خب تا الان بلاگ نداریم و میخوایم بلاگ رو هم به پروژه اضافه کنیم تا خبرها رو از طریق اون منتشر کنیم, اولین کاری که هر برنامه نویس سی شارپ که از EF توی پروژه خودش استفاده کرده اینه که بیاد کلاس پایین رو ایجاد کنه:

کلاس ها رو ساده در نظر میگیرم فعلا
کلاس ها رو ساده در نظر میگیرم فعلا

خب بعدش کلاس های مورد نیازمون رو ساختیم تا بتونیم این رو توی یه جدولی به مدیر سایت نمایش بدیم, بعد از تمام شدن این مرحله به ما میگن یه فرم جستجو هم بزارین بالای این جدول تا بر اساس عنوانی که وارد میکنیم بتونیم جستجو رو هم انجام بدیم, خب برای این کار مجبور شدیم بیایم یه مدل برای این صفحه ایجاد کنیم:

تا اینجای کار هیچ مسئله خاصی وجود نداشت چون توی حالت عادی ما میایم همین مدل رو از UI میگیرم میدیم به سرویس و اونم با استفاده از این آیتم ها لیست رو فیلتر میکنه و نتیجه رو برمیگردونه, اما توی ساختار خودمون این بخش رو به کل حذف کرده بودیم و قرار نبود اصلا کسی برای این بخش کدی بنویسه(هدف ما هم از اول همین بود که کدهای این سبکی رو حذف کنیم که سریعتر بتونیم پروژه رو توسعه بدیم).

خب الان از کجا بدونیم چه سرویسی قراره این مدل رو بگیره و چه کاری باهاش انجام بده! ساده ترین کار این بود که بیایم از Attributeها استفاده کنیم و یه خورده کلاس هامونو باهوش تربیت کنیم مثل کاری که Ef توی کلاس اول انجام داد و فهمیدم که این مدل قراره به کدوم جدول وصل بشه, تغییرات رو دادیم و کلاس نهایی شد این شکلی:

خب این طوری مسئله حل شد, توی زیر ساخت خودمون وقتی درخواست رسید سمت سرور میدونستیم که قراره این رو به چه سرویسی ارسال کنیم, برای این که بخوایم این مدل رو تشخیص بدیم از چی استفاده کردیم؟ بله ساده ترین روش اولین روش میشه, رفتیم سراغ Reflection و با کمک این تیکه کد کلاس اصلی رو پیدا میکردیم باهاش:

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

دومین راه ساده برای حل این مسئله این بود که دست به گریبان ساختار Dictionary بشم که سر زمان خیلی کمک بزرگی بود, در نهایت کد بالا به شکل زیر تغییر کرد:

این بار وقتی کد رو اجرا کردم توی اجرای اول خب تغییر خاصی اتفاق نیفتاد ولی سر درخواست های بعدی که مکررا داشت ارسال میشد این زمان رسید به چیزی در حدود 35 نانوثانیه, که خب خیلی توی زمان اجرا تاثیر داشت یعنی چیزی در حدود 157 بار سریعتر اجرا شدن در درخواست های بعدی!!! ولی این روش یه مشکل جدی داشت و اونم این بود که داشت مصرف Memory رو بالا میبرد, عملا مشکل رو از یه نقطه برداشتم و فرستادم یه نقطه دیگه(یهو یادم افتاد مرغ و خروس ها چطوری جای خودشون رو تمیز میکنن)

وقتی به این نقطه رسیدم ظاهرا تمام چیزی که میخواستم بهش رسیده بودم ولی واقعیت این بود که هنوز خیلی جای کار داشت, پس دست به دامن خود سی شارپ شدم, وقتی داشتم کد های خود سی شارپ رو توی گیت هاب میخوندم که ببینم هر قسمتی رو چیکار کردن که سریعتر شده به یه کلاس خیلی جالب خوردم:

این کلاس تمام چیزی که لازم داشتم رو به من داد, اومدم و یه کلاس جدید ساختم که هنوز از همون Reflection استفاده میکرد اما این بار به صورت generic و static و در نهایت شد این کلاس:

خب حالا نوبت اجرای پروژه با این کد بود, وقتی اجرا کردم دیدم متوسط زمان لازم برای پیدا کردن مدل اصلی از روی SearchContext نزدیک 4 نانوثانیه شد!!!! یه بار دیگه مموری رو هم بررسی کردم و بر خلاف روش دوم سربار مموری هم وجود نداشت.

ولی چه چیزی باعث شد که حتی بتونیم از Dictionary هم سریعتر باشیم؟

اولین موضوع این هستش که وقتی داریم از Dictionary استفاده میکنیم برای افزودن و یا جستجوی یه آیتم باید سربار محاسبه هش کد رو هم لحاظ کنیم, مورد دوم که اصلی ترین مورد هستش مربوط به ماهیت خود سی شارپ هستش, کلاس داخلی BaseModelTypeFinder به صورت Generic هستش و چون Static هستش باعث میشه تا در زمان اجرا به ازای هر T یک Type جدید برامون ایجاد کنه و اگر دقت کنین ModelType رو هم به صورت Readonly ایجاد کردم تا فقط در زمان ایجاد این Type یک بار صدا زده باشه.

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

با Stopwatch این مقایسه رو انجام دادم که خب ممکنه توی سیستم های مختلف عدد های یکسانی نیاد
با Stopwatch این مقایسه رو انجام دادم که خب ممکنه توی سیستم های مختلف عدد های یکسانی نیاد
سی شارپperformancereflection
mostafavakili.ir
شاید از این پست‌ها خوشتان بیاید