لا تكون قوة أي نظام إلا بقوة أضعف عنصر فيه. حتى لو كانت معظم قاعدة الشيفرة فعالة، فإن دالة واحدة سيئة التحسين أو خوارزمية غير فعالة يمكن أن تسحب أداء النظام بأكمله إلى الأسفل. يمكن أن تؤدي هذه العقبة إلى أوقات استجابة بطيئة، وزيادة استهلاك الموارد، وتدهور تجربة المستخدم. غالبًا ما تتراكم مشكلات الأداء، حيث يؤثر مكوّن بطيء واحد على المكونات الأخرى، مما يضخّم المشكلة. لذلك، يجب على المطورين التركيز على تحسين كل جزء من البرمجيات، وضمان عدم وجود حلقات ضعيفة يمكن أن تقوّض أداء النظام واستجابته.

غالبًا ما نميل إلى تعقيد الحلول أكثر من اللازم. فسعيًا وراء الأناقة أو المرونة أو ثراء الميزات، قد يُدخل المطورون تعقيدًا غير ضروري – مثل البنى المعقدة بشكل مفرط، أو التجريدات المبالغ فيها، أو المكونات الثقيلة. وبينما قد تبدو هذه الحلول متطورة، إلا أنها قد تؤدي إلى زيادة أعباء صيانة الكود، وصعوبة أكبر في تصحيح الأخطاء، وأداء أبطأ بسبب الأعباء الإضافية.

غالبًا ما تكون الأساليب الأبسط والأكثر مباشرة أكثر كفاءة وأسهل في الصيانة، مما يبرز قيمة الوضوح والبساطة في تطوير البرمجيات. لهذا السبب أذكّر نفسي باستمرار باختصار قديم جيد، KISS – أي أبقِه بسيطًا بغباء. هذا مبدأ أساسي أرغب في غرسه في أي مطوّر.

لكن ما هو الأداء حقًا؟

عندما أتحدث عن الأداء، فأنا أعني مدى كفاءة وسرعة تنفيذ تطبيق برمجي للمهام، ومعالجته للبيانات، واستجابته لمدخلات المستخدم. يُعد الأداء العالي أمرًا بالغ الأهمية لتوفير تجربة مستخدم سلسة، خاصة في التطبيقات التي تتطلب معالجة فورية. وتلعب تقنيات العرض دورًا مهمًا في الأداء، لا سيما في التطبيقات كثيفة الرسومات. ويضمن العرض الفعّال عرض الصور والرسوم المتحركة وواجهات المستخدم بسرعة وسلاسة، دون تأخير أو تباطؤ. ويتضمن تحسين الأداء في هذا السياق تقليل العبء الحسابي، وخفض استهلاك الذاكرة، واستخدام خوارزميات فعّالة قادرة على التعامل مع المشاهد والتفاعلات المعقدة دون المساس بالسرعة أو الجودة البصرية.

في مجال الرسومات والعرض، تُعدّ العناصر الأولية والتجميع (Batching) من المفاهيم الأساسية التي تؤثر بشكل كبير على الأداء. تشير العناصر الأولية إلى الأشكال أو العناصر الأساسية، مثل النقاط والخطوط والمثلثات، التي تُستخدم لبناء كائنات رسومية أكثر تعقيدًا. أما التجميع، فهو تقنية يتم فيها تجميع عناصر أولية متعددة ومعالجتها في عملية واحدة بدلاً من معالجتها بشكل فردي. وهذا يقلل من عدد استدعاءات الرسم إلى وحدة معالجة الرسومات (GPU)، والتي قد تشكل عنق زجاجة في أداء خطوط العرض.

لا يُقاس الأداء بالسرعة والاستجابة فحسب، بل أيضًا باستهلاك الذاكرة. يُعد الاستخدام الفعّال للذاكرة أمرًا بالغ الأهمية لضمان تشغيل التطبيق بسلاسة، لا سيما على الأجهزة ذات الموارد المحدودة. يمكن أن يؤدي الاستهلاك المرتفع للذاكرة إلى أداء أبطأ، حيث قد يحتاج النظام إلى استخدام مساحة القرص لذاكرة إضافية (عبر التبديل)، وهو أبطأ بكثير من ذاكرة الوصول العشوائي (RAM). بالإضافة إلى ذلك، يمكن أن يؤدي الاستخدام المفرط للذاكرة إلى تعطل التطبيق أو تباطؤه، خاصة عند تشغيله إلى جانب برامج أخرى.

HMI Qt QMLoptimisation performance

غالبًا ما توجد مقايضة بين السرعة والذاكرة، حيث يمكن أن يؤثر تحسين أحدهما على الآخر. على سبيل المثال، يمكن تصميم الخوارزميات لتعمل بشكل أسرع باستخدام المزيد من الذاكرة لتخزين البيانات المحسوبة مسبقًا، أو تخزين النتائج مؤقتًا، أو استخدام هياكل بيانات أكثر شمولًا. يقلل هذا النهج من الحاجة إلى الحسابات المتكررة، مما يسرّع التنفيذ لكنه يزيد من استهلاك الذاكرة. وعلى العكس، يمكن للمطورين تقليل استخدام الذاكرة باستخدام هياكل بيانات أو خوارزميات أكثر إحكامًا، مما قد يتطلب حسابات إضافية، وبالتالي يبطئ البرنامج. تحقيق التوازن في هذه المقايضة أمر بالغ الأهمية في تطوير البرمجيات، حيث يعتمد النهج الأمثل على المتطلبات والقيود المحددة للتطبيق، مثل سعة الذاكرة لمنصة الهدف وتوقعات الأداء.

لا ينبغي أن ننسى أن الأداء أصبح يُقاس بشكل متزايد من خلال استهلاك الطاقة، إذ أدرك السوق الدور الحاسم لكفاءة الطاقة في تحسين كل من الاستدامة والكفاءة التشغيلية للتطبيقات، لا سيما للأجهزة التي تعمل بالبطاريات.

مزالق الأداء الشائعة

استخدام حلول معقدة

عادةً ما يكون السبب في ذلك هو تأثيرات التظليل (shader): التمويه، الشفافية، الإخفاء، التلوين، الظلال. في كل مرة يتغير فيها أي شيء في المشهد، يتعين على Qt Quick إعادة رسم كل شيء من الصفر، بما في ذلك العناصر التي بقيت كما هي. عادةً، نحتاج إلى تحديد الأجزاء التي يمكن وينبغي تبسيطها في المشهد يدويًا.

HMI Qt QMLoptimisation performance

التحسين المُبكر

التحسين المبكر هو ممارسة التركيز على تحسين أجزاء من النظام قبل الفهم الواضح لمكان اختناقات الأداء الفعلية. وغالبًا ما يؤدي هذا النهج إلى إهدار الجهد وإضافة التعقيد، إذ قد يقضي المطورون وقتًا في تحسين أجزاء من الشيفرة ذات تأثير ضئيل على الأداء الإجمالي.

عدم استخدام أداة تحليل الأداء

بدلاً من التحسين المبكر، ينبغي أن يسترشد بالتحليل والتعريفات والبيانات الفعلية، التي تكشف عن مواضع أوجه القصور الأكثر أهمية.

لتحليل استهلاك المعالج CPU والذاكرة RAM لتعليمات QML / JavaScript البرمجية، ينبغي استخدام محلّل QML المضمَّن في Qt Creator. إلا أنه لا يحلّل تعليمات C++ البرمجية. ومع ذلك، يكفي أي محلّل عام الأغراض لتحليل تعليمات C++ البرمجية (مثل Valgrind Callgrind).

لتحليل GPU / المُصيّر، يمكنك تعيين متغيرات بيئة محددة لتمكين إحصاءات عرض Quick Scene Graph – والتي ستتعرف عليها لاحقًا في هذه المقالة.

ارتباطات مفرطة

ينشئ كل ارتباط في QML سياق تقييم يراقب التغييرات في الخصائص التي يعتمد عليها. وإذا كان تطبيقك يحتوي على عدد كبير من الارتباطات، خاصة على الخصائص المتغيرة بشكل متكرر، فقد يؤدي ذلك إلى عبء كبير.

HMI Qt QMLoptimisation performance

يمكن أن تتسبب الارتباطات التي تعتمد على ارتباطات أخرى في حدوث تفاعل متسلسل حيث يؤدي تغيير في خاصية واحدة إلى تحديثات عبر ارتباطات متعددة. وقد يؤدي ذلك إلى عمليات إعادة حساب متتالية حتى عندما لا يؤثر تغيير الخاصية الأساسية على النتيجة.

قد يؤدي الاستخدام المكثف للارتباطات (bindings) داخل المكونات التي تتكرر مرات عديدة (مثل ListView) إلى مضاعفة تكاليف الأداء. فقد يكون لكل عنصر متكرر مجموعة ارتباطات خاصة به، مما يؤدي إلى عدد كبير من عمليات التقييم المتزامنة. انقل العمليات الحسابية المعقدة خارج الارتباطات إلى دوال أو خصائص لا يتم حسابها إلا عند الضرورة القصوى. فكّر في تعيين الخصائص مباشرة أو استخدام ارتباطات محدودة للخصائص الحرجة فقط.

الإفراط في الرسم

أسهل طريقة لتكون أسرع هي الرسم بشكل أقل. من الناحية المثالية، نرغب في أن يقوم Qt Quick برسم جميع العناصر التي يمكن للمستخدم رؤيتها وفقط تلك العناصر. تذكر أن Qt Quick يجب أن يعيد رسم كل عنصر تكون خاصية الظهور (visible) فيه مضبوطة على true. ينبغي عليك إخفاء العناصر المحجوبة – ويمكن القيام بذلك بسلاسة باستخدام العروض (views) مثل StackView.

يعمل بشكل جيد على سطح المكتب، لكن معدل الإطارات ينخفض على الأنظمة المدمجة

تمتلك الأجهزة المدمجة/المحمولة مجموعة فرعية من تسريع الأجهزة التي توفّرها أجهزة سطح المكتب. تذكّر دائماً بتحليل النظام ودراسته على الجهاز المستهدف لتجنّب المفاجآت غير السارة لاحقاً.

عدم فهم كيفية عمل أدوات العرض

سواء كان الأمر يتعلق بـ OpenGL أو Vulkan أو Qt Quick Renderer – فإن عدم الإلمام بالمفهوم العام لعرض خط أنابيب الرسوميات سيرتد عليك عاجلًا أم آجلًا. لا تحتاج إلى أن تكون خبيرًا في العرض، لكن استيعاب كيفية عمله في العمق سيوفر عليك الكثير من المتاعب لاحقًا وسيتيح لك استخراج أقصى كفاءة من شريحة الرسوميات.

HMI Qt QMLoptimisation performance

كمبدأ عام، يمكننا القول إن كثرة تغييرات الحالة في خط المعالجة تقتل أداءه. وبمصطلح تغييرات الحالة، يمكننا إدراج أي تغيير في الأنسجة أو المخازن المؤقتة أو العتامة أو القص أو التظليل أو أهداف العرض. ومن الناحية المثالية، استهدف رسم أكبر عدد ممكن من العناصر دون تغييرات في الحالة.

تذكّر: وحدات معالجة الرسومات (GPUs) بارعة في رسم عدد كبير من العناصر الأولية دفعة واحدة.

لهذا فإن استخدام أكبر عدد ممكن من العناصر الأولية يُعدّ فعالاً.

Qt Quick Renderer

HMI Qt QMLoptimisation performance

مخطط المشهد

مخطط المشهد (Scene Graph) هو بنية البيانات الأساسية التي يستخدمها مُصيّر Qt Quick لإدارة العناصر المرئية وعرضها. وهو ينظّم عناصر QML في بنية شجرية، حيث تمثّل كل عقدة عنصراً مرئياً، مثل مستطيل أو صورة. كما يتيح طرقاً لتحسين العرض.

التجميع على دفعات

تجميع الدفعات يجمع أوامر عرض متعددة في عملية واحدة لتقليل الحمل على وحدة معالجة الرسومات. لا يمكن تجميع جميع أوامر العرض – فقط تلك التي تشترك في نفس حالة خط الأنابيب. تخيل أن أي تغيير في العتامة أو التظليل أو النسيج أو القص أو هدف العرض يؤدي إلى دفعة جديدة (مما يؤدي إلى أداء أسوأ).

يستخدم التجميع أيضًا تقنيات مثل أطالس النسيج لتقليل عدد تبديلات النسيج، مما يساعد على تحسين الأداء. ستستخدم عناصر Image وBorderImage ذلك ما لم تكن الصورة كبيرة جدًا.

يأتي الأداء الجيد من التجميع الفعال، مع أقل قدر ممكن من إعادة تحميل الأشكال الهندسية مراراً وتكراراً.

العناصر الأولية المعتمة

يفصل محرك العرض بين العناصر الأولية المعتمة والعناصر الأولية التي تتطلب مزج ألفا. ومن خلال النظر إلى حالة المادة لكل عنصر أولي، ينشئ محرك العرض دفعات معتمة. تتضمن مجموعة العناصر الأساسية في Qt Quick عناصر مستطيلة بألوان معتمة وصور معتمة بالكامل، مثل ملفات JPEG أو BMP (وليس PNG). ومن الفوائد الأخرى أن العناصر الأولية المعتمة لا تتطلب تفعيل GL_BLEND، وهو أمر قد يكون مكلفًا للغاية، خاصة على وحدات معالجة الرسومات المدمجة والمحمولة.

تصحيح الأخطاء

من خلال ضبط متغير البيئة QSG_RENDERER_DEBUG=render، سيُخرج العارض إحصاءات حول مدى جودة التجميع، وعدد الدفعات المستخدمة، والدفعات التي تم الاحتفاظ بها، وأيها معتم وأيها غير معتم. وعند السعي لتحقيق الأداء الأمثل، ينبغي أن تتم عمليات الرفع فقط عند الحاجة الفعلية، وأن يكون عدد الدفعات أقل من 10، وأن يكون 3-4 منها على الأقل معتمًا.

يؤدي ضبط QSG_VISUALIZE على batches إلى إظهار الدفعات في أداة العرض، ويرسم clip مناطق حمراء للإشارة إلى القص، ويعرض changes التغييرات عبر وميض طبقة تراكب بلون عشوائي، ويبرز overdraw حالات التراكب المفرط في الرسومات ثلاثية الأبعاد.

هل أنت مهتم بالعمل في Qt/QML؟

انضم إلينا

دراسة حالة

دراسة حالة رائعة يمكنني تقديمها لكم فيما يتعلق بتحسين أداء QML هي مهمة مشروع قمت بتنفيذها لأحد عملائنا.

تم تصميم واجهات المستخدم للمشروع في Figma، وهي أداة شائعة لإنشاء ومشاركة واختبار التصاميم للعديد من التطبيقات. وتأتي مع الكثير من الخصائص للعمل بها. إحداها تأثيرات الظل. تُترجم هذه الظلال الخارجية أو الداخلية إلى خاصية box-shadow في CSS.

HMI Qt QMLoptimisation performance

كان علينا تحقيق التأثير نفسه في QML – وقد نجحنا في ذلك.

كان نهجنا الأول استخدام مكوّن DropShadow من وحدة التأثيرات الرسومية في Qt. كانت النتيجة البصرية رائعة، لكن التأثير على الأداء في الجهاز المدمج المستهدف كان هائلاً.

كمنهج ثانٍ، جربنا تظليلًا مخصصًا. أنتج تأثيرًا جميلًا جدًا، مع تأثير مقبول على الأداء. ولكن مع نمو النظام، أصبح المزيد والمزيد من العناصر يستخدم هذا التظليل، مما جعل تأثير الأداء أقل قبولًا.

لا يمكن تجميع عنصر يستخدم طبقة/تظليل أثناء العرض. وكان ذلك سبب عنق الزجاجة لدينا. قررت أننا بحاجة إلى مقايضة الدقة المطابقة تماماً مع Figma بحل جيد بما يكفي وسريع وموفر للذاكرة.

بما أنني امتلكت خبرة في تطوير الألعاب، فقد عرفت مفهوم القياس بتقسيم 9 شرائح جيداً.
إنها تقنية لإنشاء صور قابلة للتوسع يمكن تغيير حجمها دون تشويه الأجزاء المهمة من الصورة (الزوايا). ولحسن الحظ، يوفر Qt Quick مكوّن BorderImage الذي يطبّق هذه التقنية.

HMI Qt QMLoptimisation performance

طلبت بالفعل صوراً مقسمة إلى 9 شرائح للظلال المعروضة مسبقاً من مصممينا، ولم يكن ذلك عبئاً كبيراً عليهم لأن Adobe Photoshop (والعديد من الأدوات الأخرى) يوفر إضافات محرر الشرائح التسع لإنشاء مثل هذه الأصول بسهولة.

استبدلت بسرعة نهج التظليل بصورة حدودية في تنفيذنا لظل الصندوق وحدثت معجزة: تضاعف معدل الإطارات ثلاث مرات، ليصل إلى 60 إطارًا في الثانية من 20 إطارًا في الثانية. حتى استهلاك الذاكرة انخفض إلى النصف، ليصل إلى 600 ميغابايت من 1200 ميغابايت. نظرًا لأن كل عنصر يستخدم الظل كان يُعرض في طبقة/مخزن مؤقت منفصل، كان حجم الذاكرة المستهلكة لمثل هذا التنفيذ ضخمًا جدًا.

بطبيعة الحال، كان نهج صورة الحدود مقايضةً مقابل الدقة. لكن لم يكن مفاجئاً أن يلاحظ الجميع أداءً أفضل بكثير. كان التدهور في الجودة البصرية لتأثير الظل، في الواقع، ضئيلاً، خاصةً مقارنةً بمكاسب الأداء التي حققناها.

غالبًا ما يكون الكمال عدوًا للجيد.

الملخص

لا يوجد حل واحد لمشكلات الأداء. كل تطبيق وسيناريو مختلف، ويتطلب أساليب مخصصة للتحسين.

البساطة تؤدي إلى أداء أفضل، وصيانة أسهل، وأخطاء أقل – لكنها تأتي من الخبرة والكفاءة.

تلعب الخبرة دورًا حيويًا في اتخاذ هذه القرارات – يمكن للمطورين المتمرسين تحديد المخاطر المحتملة، واختيار الأدوات المناسبة، وموازنة المقايضات بفعالية، مما يضمن أن يكون الحل ليس وظيفيًا فحسب، بل أيضًا فعالًا وقابلًا للصيانة.