عندما بدأت كتابة السطور كان يفترض أنني اترجم المقالة من موقع About .
الرابط الاصلي : http://delphi.about.com/od/objectpascalide/a/speedsize.htm
ولكني بعدها أقحمت أفكاري الخاصة وأمثلتي عليها على كل حال أعتقد انني أحتفظت بمعظم العناوين .
عشر نصائح لتحسين حجم وسرعة تطبيقات دلفي
رؤية عروة عيسى . 17/3/2005
ما سأذكره في هذه العجالة هو مجموعة أفكار مفيدة لتحسين حجم وسرعة برامج دلفي .
بداية , فإن تحسين المشروع لأبعد حد (Optimization) يعتبر من الأمور المهمة والمرغوبة في هندسة البرمجيات بشكل عام , ولكن أحذر ؟
فهي دائما كانت في مواجهة أمور أخرى بالغه الأهمية مثل الإستقرارية (stability) والتوسعية (extensibility) , التي تجبرنا أحيانا على غض الطرف قليلا عن الاداء مقابل بعض المكاسب الأخرى (كما قلت في مقالة البرمجة الغرضية التوجه جاءت الـOOP بأفكار التوسعية ولم تعطي الأولوية رقم 1 للأداء ."لاحظ أن C أسرع من C++ كما هو معروف , رغم أن الأخيرة هي الداعمة للـoop , ولكن من المؤكد ان بناء تطبيقات ضخمة وإمكانية إعادة إستخدام الشفرة من نتائج الoop الأساسية ") .
المهم الآن . .. موضوعنا اليوم عن الاداء . مبدأيا السرعة والحجم , رغم وجود امور مهمة اخرى في إدارة الذاكرة مثلا ولكن لن تكون محور الحديث .
يتولى مترجم دلفي في معظم الاحيان إجراء التحسينات(optimimization) على المشروع , ولكن ذلك قد لا يكون كافيا في كثير من الاحيان .
هذه بعض الأفكار التي يمكنك وضعها بالحسبان عند تطوير تطبيقات دلفي يراعى فيها الحصول على ملفات صغيرة الحجم سريعة التنفيذ :
*************************************
1- حافظ على نظافة شفرتك :
ربما يصعب إلى حد ما الجزم فيما إذا كانت الشفرة نظيفة اولا , ولايمكن تحديد ذلك دون التعمق في آلية تنفيذ الامور من الخلف , قد لاتستطيع دائما الجزم أي شفرة انظف من الثانية ولكن أظن أن ذلك سيكون واضحا في كثير من الحالات :
راقب الشفرتين التاليتين
function calc(a,b:integer):boolean; var c1,c2,c3,c:integer; r:boolean; begin c1:=a*b; c2:=a+b; c3:=a-b; c:=c1+c2+c3; if c>50 then r:=true else r:=false; result:=r; end;
وهذا يقوم بنفس الوظيفة :
function calc(a,b:integer):boolean; begin result:=((a*b)+(a*2))>50; end;
من برأيك الشفرة الأفضل , وأي منهما أسرع بالتنفيذ ,
هل لاحظت ان عدد المتحولات الإضافي سيحجز أمكنة لاداعي لها بالذاكرة ويستهلك مواردك ويبطيء تنفيذك دون مبرر ؟, . في هذا المثال البسيط لن تجد فرق كبير , لكن لوكنا نتعامل مثلا مع برنامج متحولاته من النوعBitmap بالتأكيد كل واحدة منها ستأخذ حجم كبير جدا في الذاكرة لإدارة الصور المخصصة لها , عندها من الخطورة كتابة هكذا نوع من الشفرات الغير مدروسة .
ملاحظة : من المهم الحفاظ على تسميات واضحة للمتغيرات والعناصر ويفضل تجنب إستخدام التسميات الإفتراضية مثل "Button1" التي تغرقك فيما بعد في بحر من اسماء المكونات المشابهة والتي يصعب معرفة ماذا تمثل كل منها
- يفضل وضع طريقة واحدة واضحة لتسمية المتغييرات , مثلا بداها بحرف يحدد النوع , أو الصنف الاب .. مثلا:
SMessage و SName للمتغيرات النصية (String)
iCount و iRate للمتغيرات الصحيحة ,
Tobject للأصناف .
Frm_Main للنماذج , (لاحظ , البدء ب Frm يسهل عليك التعامل معها لاحقا )
*************************************
2- إستخدم توجيهات المترجم المفيدة :
خذ مثلا التوجية {$O+} .
والذي يعني Optimization On .. تاكد دائما من ان هذا التوجيه مفعل (on)
مع هذا التوجيه سيولد مترجم دلفي شفرة اكثر فاعلية . بالتاكيد ستحصل على بعض الصعوبات الإضافية في التنقيح عند إستخدام مثل هذا توجيه .
*************************************]
3- جرب ترجمة برامجك ب Run_Time Packlages :
ربما تعاملت مسبقا مع رزم المكونات ( packages) تملك هذة الرزم نوعان Run_Time و Design_Time ..
رزم الـ Design_Time هي الرزم الإفتراضية , وهي جيدة ولكنها تحمل حجم إضافي لمعلومات سيتم إستخداما في زمن التصميم فقط , وبالتالي من غير المفيد ان يحمل الملف التنفيذي النهائي هذا الحجم الإضافي وهو لن يستطيع الإستفادة منه في زمن التشغيل .
إستخدام Run_Time Packages في حال توفرت مع المكونات حتما سيحسن حجم الملف وأداءة ,
خذ مثلا المكونات التي تحوي محررات خصائص كالتي يمكنك الوصول إليها من ضابط الكائنات , تخزن معلومات هذه المحررات معها مع عدم الفائدة منها في التطبيق التنفيذي النهائي .
. كما أن إستخدام Run_Time مفيد جدا في حال كان لدينا أكثر من تطبيق يستخدمون نفس المكتبات , حيث يكفي توفرها مرة واحدة لكل التطبيقات التي تستخدمها .
المشكلة الاساسية أنه عند استخدام Run_Time_Packages يجب نشر هذة المكتبات مع التطبيق النهائي وكأنها مكتبات DLL .
مثال : إذا كنت تتبادل كثيرا ملفات مع زملائك اللذين يملكون دلفي على اجهزتهم (أي حتما ستضمن وجود نسخة من مكتبات الأدوات القياسية عندهم ) بإمكانك عندها إرسال تطبيقات صغيرة جدا لهم تعتمد على مكتبات Run_Time .
من قائمة Project -> options ->packages إختر الخيار السفلي :
Build with run time packages
وستلاحظ بعدها أن الملف التنفيذي الناتج غدا صغيرا جدا .
(لاحظ الملف المرفق بالمشاركة , ملف نظامي تماما بحجم 16K دون أي خدع بالشفرة)
*************************************
4 – لاتقم بإنشاء كل النماذج منذ بدء التشغيل
تقوم دلفي إفتراضيا بإنشاء النماذج كلها عند التشغيل ( Create All forms at startup ) , ولكن ذلك سيجعل كل النماذج مخزنة بالذاكرة على الرغم من أننا قد لانستخدمها , أو على الأقل لن نستخدمها معا في وقت واحد .
مثال : قد تقوم بإنشاء نموذج "حول"(About) يحوي صورة جميلة لشعار شركتك , ومعلومات عنك , وبريدك الإلكتروني الخ ..
هذا النموذج (About) سيراه المستخدم مرة في العمر , وبالتالي لايوجد داعي لأن يبقى موجودا بالذاكرة كل مرة تشغل فيها البرنامج خاصة انه قديحوي صور بحجم كبير قد تأخذ حيز من الذاكرة من الأفضل ان يبقى لخدمة امور أكثر اهمية , وهذا ماقد يؤثر على سرعة تطبيقك وإستجابتة .
لذلك بإمكانك خلق الذاكرة الخاصة به لحظة ضغط المستخدم على الزر وتحريرها بعد إغلاق النموذج .
كيف نقوم بذلك :
من قائمة دلفي إختر projects -> options
ستظهر أمامك النافذة السابقة , القسم العلوي يحوي مربع إختيار النموذج الأساسي للمشروع , وتحتة مباشرة ستجد قائمة النماذج لديك تحت عنوان Auto_Create .
بكل بساطة حدد النماذج التي لاتريد ان يتم إنشاءها تبقائيا ثم إضغط على زر (>) الذي ينقلها إلى اللائحة المجاورة . أي أنها ستبقى موجودة ,ولكنها لن تنشأ وتحجز مكان بالذاكرة حتى تقوم بطلب ذلك من شفرتك .
سيلزمك الان سطر إضافي كي تستدعي النموذج (Form) , وهو سطر إنشاء الفورم في الذاكرة :
procedure TForm1.Button1Click(Sender: TObject); begin with Tform2.create(nil) do show; end;
أو
procedure TForm1.Button2Click(Sender: TObject); var frm2:Tform2; begin frm2:=TForm2.Create(Application); frm2.Show; end;
ولاتنسى تحرير الذاكرة عند الإغلاق :
form2.free;
*************************************
5- إستخدم توابع النظام (Windows API) بدلا من كتابة شفراتك الخاصة .
لإنها أسرع , وتوفرا كثيرا بالحجم . كما انها حتما مدروسة اكثر من شفراتك مهما تعبت عليها .
مثال :
لنقل التركيز إلى العنصر التالي (مشابة لفعل الزر TAB) يمكننا كتابة شفرة شبيهه بالتالية :
procedure Do_Tabs(Sender: TObject; var Key: Word); var CtlCount: integer; begin if Key = VK_RETURN then if (Sender is twincontrol) then begin // ÊÚÏíá åäÇ if (sender as twincontrol).TabOrder = num_of_winControl_parent(sender) - 1 then begin // for CtlCount := 0 to (sender as twincontrol).Parent.parent.ControlCount - 1 do // if ((sender as twincontrol).Parent.parent.Controls[CtlCount] is twincontrol) then if ((sender as twincontrol).Parent.parent.Controls[CtlCount] as TWinControl).TabOrder= // ((sender as twincontrol).Parent.TabOrder +1) then // (sender as twincontrol).Parent.SetFocus; end else begin for CtlCount := 0 to (sender as twincontrol).Parent.ControlCount - 1 do if ((sender as twincontrol).Parent.Controls[CtlCount] is twincontrol) then if ((sender as twincontrol).Parent.Controls[CtlCount] as twincontrol).TabOrder = (sender as twincontrol).TabOrder + 1 then if (((sender as twincontrol).Parent.Controls[CtlCount] as twincontrol).Enabled) and (((sender as twincontrol).Parent.Controls[CtlCount] as twincontrol).Visible) then begin ((sender as twincontrol).Parent.Controls[CtlCount] as twincontrol).SetFocus; break; end; end;
ما رأيك الآن بتجربة هذا , الذي يقوم بأفضل من عمل الشفرة السابقة :
Form1.Perform(WM_NEXTDLGCTL, 0, 0);
خذ مثلا الحجم الصغير الذي سينتج معك عند بنائك لنوافذ المشروع بإستخدام API من نظام التشغيل بدلا من إستخدام مكتبة VCL . بغض النظر عن صعوبة أو سهولة أو الضريبة المتعلقة على ذلك , تعتبر توابع API من أسلحة المبرمج الذي يجب التسلح بها .
*************************************
6- حسن إلى أبعد حد الموارد التي تستخدمها في برنامجك :
مثل الصور والأيقونات وملفات الصوت .. الخ ..
إبتعد عن التنسيقات الغير مضغوطة للصور مثل BMP وإستبدلها ب GIF أو JPEG إن أمكن .
تاكد على كل حال من خصائص أخرى مثل عمق الألوان – لاداعي لإستخدام صورة ب 64K color عندما تكفي صورة ب 256 color .
ينصح بتحويل صور jpeg الأقل من 255 color إلى صور Gif الأصغر حجما .
(راجع مكتبة الادوات للحصول على دعم لصور GIF في العنصر image )
- خدعة صغيرة :
عندما تتعامل مع المكون image في دلفي , فإنك على الغالب لاحظت الخاصية stretch .
هذه الخاصية تقوم بمد الصورة حسب حجم ال image التي تعطيها أياه ,
بإمكانك بالتالي إستخدام صورة تدرج لوني بطول صغير (وليكن مثلا 150 بيكسل) وعرض (1 بيكسل فقط) ثم ضبط خاصية stretch إلى true ومد الصورة على الفورم لتحصل على شكل جميل بحجم صغير .
يفضل في حال كنت تستخدم الصورة نفسها اكثر من مرة في أكثر من مكان أن لا تكرر الصورة نفسها في العنصر image لإن ذلك سيضاعف حجم التخزيم كل مرة , ويعتبرها صورة جديدة , يفضل التعامل مع imageList عوضا عن ذلك , أو تحميل الصورة من الملف (LoadFromFile)
*************************************
7- عندما تستخدم إجرائية او أثنين فقط في وحدة ما , وكانت شفرة الوحدة مفتوحة امامك . قم بنسخ شفرة الإجرائيات اللازمة منها وضعها في برنامج بدلا من إضافة الوحدة إلى قائمة uses .
لإنه بمجرد استخدام أحد الأجرائيات المعرفة ضمنها وإضافتها إلى المشروع سيتم حجز الحجم الكامل لها وتضاف كلها إلى الملف التنفيذي وليس فقط الإجرائيات التي إستخدمناها في المشروع .
يمكنك القيام بهذه الطريقة في الكثير من الحالات .
*************************************
8- خفف من استخدام المكونات إن استطعت وخاصة المكونات اللامرئية non_visual components حيث يمكنك إضافة أكوادها يدويا إلى برنامجك , دون التكلف بوضع العنصر الذي سيجر وراءه عدة وحدات ويشغل حجم ربما لست مضطر لتخسرة .
حتى المكونات العادية يمكنك التخفيف منها لاداعي مثلا لوضع مكون مستعرض الإنترنت على النموذج لمجرد عرض رابط لموقعك بإمكانك بسطر واحد القيام بإستدعاء ال internet explorer من ويندوز وفتح موقعك من خلالة .
*************************************
9- تعلم وأستخدم تقنيات سريعة في شفرتك .
مثل استخدام الاسمبلي المضمن مثلا والذي يكفل لك العمل بسرعة رهيبة .
أو إضافات inline في النسخ الجديدة من دلفي (Delphi 2005 مثلا )
وأحذر تمام الحذر عند الحلقات الطويله (long loops) أو التوابع الكثيرة الإستدعاء , او داخل المؤقتات وغيرها من الامكنة التي يتضاعف فيها الخطأ الصغير ليسبب بطء في التنفيذ لاداعي له .
*************************************
10 – إختر خوارزمية عمل مناسبة .
دائما تذكر "أن يقوم الحاسب بإختيار قيمة من مجموعة قيم , أسرع من أن يقوم بحساب هذه القيمة معظم الأحيان"
مارأيك بجداول الجيب (Sin) والتجب (Cos) التي تستخدم في برامج المحاكاة التي تتطلب سرعة عالية .
أستبدل البحث الخطي مثلا بالبحث الثنائي فهو أسرع حتما
وأخيرا وليس اخرا .. بسّط شفرتك . الشفرة البسيطة أسرع بالتنفيذ , وأصغر بالحجم
الملف المرفق