كيفية احتواء مليون نجمة في iPhone



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

لتقليل استخدام الذاكرة سيكون مفيدًا في العديد من الحالات الأخرى. لذلك يظهر هذا النص على مثال مشروع صغير طرق التحسين التي يمكن أن تكون مفيدة في تطبيقات iOS المختلفة تمامًا (وليس فقط iOS-).

تم إعداد المنشور على أساس نص تقرير كونراد فيلر من مؤتمر Mobius 2018 Piter. نرفق الفيديو الخاص به ، ثم نسخة نصية في أول شخص:



سعيد للترحيب بالجميع! اسمي كونراد فيلر ، وتحت الاسم المذهل "A Million Stars in One iPhone" ، سنناقش كيف يمكنك تقليل حجم الذاكرة التي يشغلها تطبيق iOS الخاص بك. بشكل ملون وفي أمثلة.

لماذا التحسين؟


ما الذي يشجعنا بشكل عام على إجراء التحسين ، ما الذي نود تحقيقه بالضبط؟ لا نريد هذا:



لا نريد أن ينتظر المستخدم. هذا هو السبب الأول هو تقليل وقت التمهيد .

سبب آخر هو تحسين الجودة .



يمكننا التحدث عن جودة الصور والصوت وحتى الذكاء الاصطناعي. يعني "الذكاء الاصطناعي المُحسَّن" أنه يمكنك تحقيق المزيد - على سبيل المثال ، احسب اللعبة لعدد أكبر من التحركات للأمام.

السبب الثالث مهم جدًا: توفير طاقة البطارية . يساعد التحسين على استنزاف البطارية بشكل أقل. هذه مقارنة مثيرة للاهتمام ، وإن كانت من عالم Android. هنا قارن Vulkan و OpenGL ES:



والثاني هو الأسوأ الأمثل لمنصات المحمول. بمراقبة سرعة استهلاك طاقة البطارية ، يمكنك أن ترى أنه للحصول على صورة مماثلة ، أنفق OpenGL ES موارد أكثر بكثير من Vulkan.

ما نوع التحسين الذي يمكن أن يساعد هنا؟ على سبيل المثال ، في لعبة تعتمد على الأدوار ، عندما يفكر المستخدم في حركته ، يمكنك تقليل FPS إلى الصفر. إذا كان لديك محرك ثلاثي الأبعاد ، فمن الحكمة تمامًا إيقاف تشغيل كل شيء بينما ينظر المستخدم إلى الشاشة.

بالإضافة إلى ذلك ، هناك أوقات لن تتمكن فيها من تنفيذ ميزة متقدمة أو أخرى من دون اتباع نهج مُحسَّن: فلن يتم سحبها ببساطة.

لا تعصب


بالحديث عن التحسين ، لا يسع المرء إلا أن يتذكر أطروحة دونالد كنوث: "يجب أن ننسى انخفاض الكفاءة ، على سبيل المثال ، في 97٪ من الحالات: التحسين المبكر هو أصل كل الشرور. على الرغم من أننا لا ينبغي أن نتخلى عن قدراتنا في هذه 3٪ الحرجة ".

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

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

المناهج


عند العمل على التحسين ، نراقب عادةً الأداء (اقرأ: تحميل المعالج) أو مقدار الذاكرة المستخدمة. غالبًا ما يتعارض هذان الخياران ، وستحتاج إلى إيجاد توازن بينهما.

في حالة المعالج ، يمكننا تقليل عدد دورات المعالج التي تتطلبها عملياتنا. كما تعلم ، فإن عدد أقل من دورات المعالج يمنحنا وقت تحميل أقل ، واستهلاكًا أقل للبطارية ، والقدرة على توفير جودة أفضل ، وما إلى ذلك.

بالنسبة لمطوري iOS ، فإن Xcode Instruments لديها أداة Time Profiler يدوية. يسمح لك بتتبع عدد دورات وحدة المعالجة المركزية التي تنفقها أجزاء مختلفة من تطبيقك. هذا التقرير ليس عن الأدوات ، لذلك لن أخوض في التفاصيل الآن ، كان هناك فيديو جيد من WWDC حول هذا الموضوع.

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

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

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



يحتوي العنصر 8 على 8 بايت ، العنصر 16 - 16 ، وهكذا.



سنقوم بإنشاء مصفوفات ، واحدة لكل نوع من أنواع الهياكل لدينا. حجم جميع المصفوفات هو نفسه - 10000 عنصر. يحتوي كل هيكل على عدد مختلف من الحقول (زيادة) ؛ الحقل n هو الحقل الأول ، وبالتالي فهو موجود في جميع الهياكل.

لنجرب الآن ما يلي: لكل مصفوفة سنحسب مجموع كل حقوله n. أي أنه في كل مرة سنجمع نفس عدد العناصر (10000 قطعة). والفرق الوحيد هو أنه لكل مجموع سيتم استخراج المتغير n من هياكل ذات أحجام مختلفة. نحن مهتمون بما إذا كان التلخيص يستغرق نفس الوقت.

والنتيجة هي ما يلي:



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

يحتوي المعالج على مخابئ L1 و L2 (أحيانًا حتى L3 و L4). يصل المعالج إلى هذا النوع من الذاكرة مباشرة وبسرعة.



توجد مخابئ لتسريع إعادة استخدام البيانات. افترض أننا نعمل مع المصفوفات. إذا كانت الصفيف الذي يحتاجه المعالج موجودًا بالفعل في أي من ذاكرة التخزين المؤقت ، فقد كان المعالج مطلوبًا بالفعل في وقت سابق. في تلك اللحظة ، طلب منهم من الذاكرة الرئيسية ، ووضعهم في ذاكرة التخزين المؤقت ، وأجروا جميع العمليات اللازمة معهم ، وبعد ذلك بقيت هذه البيانات كاذبة (لم يكن لديهم وقت يمحوها الآخرون).



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

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

وحدة المعالجة المركزية مقابل ذاكرة الوصول العشوائي: التهيئة البطيئة


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

افترض أن لدينا طريقة makeHeavyObject () تقوم بإرجاع كائن كبير. ستقوم هذه الطريقة بتهيئة المتغير lazilyCalculated.



يضبط المعدل البطيء المتغير lazilyCalculated إلى التهيئة البطيئة. وهذا يعني أنه سيتم تعيين قيمة لها فقط عندما تحدث المكالمة الأولى لها أثناء التنفيذ. ومن ثم ستعمل طريقة makeHeavyObject () وسيتم تعيين الكائن الناتج إلى المتغير lazilyCalculated.

ما هو زائد هنا؟ منذ لحظة التهيئة (وإن كان لاحقًا ، ولكن سيتم تنفيذه) لدينا كائن موجود في الذاكرة. يتم حساب قيمته ، وهو جاهز للاستخدام - فقط قم بتقديم طلب. شيء آخر هو أن جسمنا كبير ومن لحظة التهيئة ستحتل في الذاكرة نصيب الأسد من الخلايا.

يمكنك الذهاب في الاتجاه الآخر - لا تخزن قيمة الحقل على الإطلاق:



مع كل ارتباط إلى الحقل lazilyCalculated ، سيتم تنفيذ الأسلوب makeHeavyObject () مرة أخرى. سيتم إرجاع القيمة إلى نقطة الاستعلام ، بينما لن يتم وضعها في الذاكرة. كما ترى ، يعد تخزين المتغير اختياريًا.

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

دورة التحسين





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

باختيار كائن ، تقوم بتعيين المهمة بنفسك (أو ، من الناحية العلمية ، طرح فرضية): من خلال تطبيق طرق التحسين هذه أو غيرها ، يمكنك جعل جزء الرمز المحدد يعمل بشكل أسرع.

بعد ذلك ، تحاول التحسين. بعد كل تعديل ، يمكنك إلقاء نظرة على مؤشرات الأداء ، وتقييم مدى فعالية التعديل ، ومقدار ما تمكنت من التقدم.

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

اختبارات الوحدة





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

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

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

والآن للجزء الممتع ، دعنا ننتقل إلى النجوم.

مليون نجم


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



في حالة عدم وجود أضواء المدينة ، يمكن للشخص تمييز ما يصل إلى 8000 نجمة في السماء. سأحتاج إلى حوالي 1.8 ميغابايت لتخزين 8000 سجل. من حيث المبدأ ، مقبول. لكنني أردت أن أضيف تلك النجوم التي يمكن أن يراها الشخص من خلال التلسكوب - فقد اتضح حوالي 120.000 نجم (وفقًا لما يسمى كتالوج Hipparcos ، عفا عليه الزمن الآن). هذا مطلوب بالفعل 27 ميغا بايت. ومن بين الكتالوجات الحديثة في المجال العام ، يمكنك العثور على كتالوج سيبلغ عدده حوالي 2.500.000 نجمة. سوف تشغل قاعدة البيانات هذه بالفعل حوالي 560 ميغابايت. كما ترى ، مطلوب بالفعل الكثير من الذاكرة. لكننا لا نريد قاعدة بيانات فقط ، ولكن تطبيقًا يعتمد عليها ، حيث سيكون هناك ARKit و SceneKit وأشياء أخرى تتطلب أيضًا ذاكرة.

ماذا تفعل؟
سنقوم بتحسين النجوم.

أداة MemoryLayout


يمكنك تقييم حجم البرنامج ككل. ولكن بالنسبة لعمل المجوهرات مثل التحسين ، ستحتاج إلى أدوات لتقدير حجم كل بنية بيانات فردية.

يتيح لك Swift القيام بذلك ببساطة - باستخدام كائنات MemoryLayout <>. تقوم بإعلان MemoryLayout <> ، تحدد بنية البيانات التي تهمك كنوع عام. الآن ، بالإشارة إلى خصائص الكائن المستلم ، يمكنك تلقي مجموعة متنوعة من المعلومات المفيدة حول الهيكل الخاص بك.



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



StarData ، هيكلنا التجريبي ، في حالته الأولية غير المحسنة:



فيما يلي بنية بيانات مصممة لتخزين البيانات حول نجمة واحدة. ليس عليك الخوض في معنى كل عنصر من هذه العناصر. من المهم الآن الانتباه إلى الأنواع: متغيرات الطفو التي تخزن إحداثيات النجم (في الواقع ، خط العرض وخط الطول) ، العديد من Int32 لمعرفات مختلفة ، سلسلة لتخزين الأسماء وأسماء التصنيفات المختلفة ؛ هناك مسافة ولون وبعض الكميات الأخرى اللازمة للعرض الصحيح للنجمة.

نطلب خاصية الخطوة:



في الوقت الحالي ، يزن هيكلنا 208 بايت. سيتطلب مليون مثل هذه الهياكل 250 ميغابايت - وهذا ، كما تعلمون ، أكثر من اللازم. لذلك ، من الضروري التحسين.

Int الصحيح


يتم سرد حقيقة أن هناك أنواع مختلفة من Int في دروس البرمجة الأولى. تسمى Int الأكثر شيوعًا بالنسبة لنا في Swift Int8. تشغل 8 بتات (1 بايت) ويمكن تخزين القيم من -128 إلى 127. هناك أيضًا Ints أخرى:
  • Int16 بحجم 2 بايت ، نطاق القيم من -32،768 إلى 32،767 ؛
  • Int32 بحجم 4 بايت ، نطاق القيم من -2147483.648 إلى 2147483647 ؛
  • يبلغ حجم Int64 (أو Int فقط) 8 بايت ، ويتراوح نطاق القيم من -9،223،372،036،854،775،808 إلى 9،223،372،036،854،775،807.


ربما أولئك الذين شاركوا في تطوير الويب وتعاملوا مع SQL يفكرون بالفعل في هذا الأمر. ولكن نعم ، أولاً وقبل كل شيء ، اختر Int الأمثل. في هذا المشروع ، حتى قبل أن أقوم بتحسين في ذهني ، دخلت في القليل من التحسين المبكر (والذي ، كما أخبرتك للتو ، ليس هناك حاجة للقيام بذلك).

دعونا ننظر ، على سبيل المثال ، إلى الحقول ذات المعرف. نحن نعلم أنه سيكون لدينا حوالي مليون نجم - ليس بضع عشرات الآلاف ، ولكن ليس مليار. لذا ، بالنسبة لمثل هذه الحقول ، من الأفضل اختيار Int32. ثم أدركت أن 4 بايت كافية لتعويم هنا. سيشغل Double 8 ، سلسلة كل 24 ، قم بإضافتها كلها - اتضح 152 بايت. إذا كنت تتذكر ، فقد أخبرتنا في وقت سابق MemoryLayout أن 208. لماذا؟ يجب أن نتعمق أكثر.



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

لا نريد إنفاق بايت إضافي على متغير. في الوقت نفسه ، نحن نحب الفكرة المجسدة في اختياري. ماذا ستخرج؟ دعونا نحاول تنفيذ هيكلنا.

دعنا نختار قيمة يمكن اعتبارها "غير صالحة" بشكل معقول لحقل معين ، بينما تكون مناسبة للنوع المعلن. يمكن الحصول على getHipId (Int32) ، على سبيل المثال ، القيمة "-1". سيعني ذلك أن مجالنا غير مهيأ. هذه دراجة اختيارية ، والتي بدون بايت إضافي على صفر.

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



يلمح مثل هذا الشخص منا تمامًا تعقيد الحل المخترع.
أنتقل إلى StarData. استبدل جميع الأنواع الاختيارية بأخرى عادية وانظر ماذا تظهر الخطوة:


اتضح أنه بعد إلغاء الخيارات ، لم نحفظ 9 بايت (بايت لكل خيار من الخيارات التسعة) ، ولكن حتى 48. المفاجأة ممتعة ، لكني أود أن أعرف لماذا حدث هذا. وحدث ذلك بسبب محاذاة البيانات في الذاكرة.

محاذاة البيانات


تذكر أنه قبل Swift كتبنا في Objective-C ، واستند إلى C - وهذا الموقف يعود أيضًا إلى C.

عن طريق وضع أي هياكل في الذاكرة ، تضع المعالجات الحديثة عناصرها ليس في تيار مستمر (وليس "كتفا إلى كتف") ، ولكن في بعض الشبكة ضعيفة بشكل غير متجانس بفراغات. هذا هو محاذاة البيانات. يسمح لك بتبسيط وتسريع الوصول إلى عناصر البيانات الضرورية في الذاكرة.
تنطبق قواعد محاذاة البيانات على كل متغير اعتمادًا على نوعه:

  • يمكن أن يبدأ متغير من النوع char من الأول والثاني والثالث والرابع ، إلخ. بايت ، لأنه يأخذ بايت واحد فقط في حد ذاته ؛
  • يأخذ المتغير القصير 2 بايت ، لذلك يمكن أن يبدأ من الثاني والرابع والسادس والثامن ، إلخ. بايت (أي من كل بايت حتى) ؛
  • يأخذ متغير من النوع float 4 بايت ، مما يعني أنه يمكن أن يبدأ كل 4 ، 8 ، 12 ، 16 ، إلخ. بايت (أي كل رابع بايت) ؛
  • متغيرات من نوع مزدوج وسلسلة تحتل 8 بايت لكل منهما ، لذلك يمكن أن تبدأ مع 8 ، 16 ، 24 ، 32 ، إلخ. بايت
  • الخ.


كائنات MemoryLayout <> لها خاصية محاذاة تقوم بإرجاع قاعدة المحاذاة المقابلة للنوع المحدد.

هل يمكننا تطبيق المعرفة بقواعد المحاذاة لتحسين التعليمات البرمجية؟ دعونا نلقي نظرة على مثال. هناك هيكل مستخدم: بالنسبة لـ firstName و lastName ، فإننا نستخدم سلسلة عادية ، بالنسبة لـ middleName - سلسلة اختيارية (قد لا يكون لدى المستخدم مثل هذا الاسم). في الذاكرة ، سيتم وضع مثيل لهذا الهيكل على النحو التالي:



كما ترى ، نظرًا لأن الاسم الأوسط الاختياري يشغل 25 بايت (بدلاً من مضاعفات 8 24 بايت) ، فإن قواعد المحاذاة تلزمك بتخطي 7 بايت التالية وإنفاق 80 بايت على الهيكل بأكمله. هنا ، بغض النظر عن كيفية تبديل الكتل مع السلاسل ، فمن المستحيل الاعتماد على عدد أقل من وحدات البايت.

والآن مثال على المحاذاة الفاشلة:



يعلن هيكل BadAligned أولاً أنه مخفي من نوع Bool (1 بايت) ، ثم حجم النوع مزدوج (8 ​​بايت) ، وهو غير قابل للتفاعل من نوع bool (1 بايت) وأخيرًا من نوع Int (أيضًا 8 بايت). تم إعلانها بهذا الترتيب ، سيتم وضع متغيراتنا في الذاكرة بحيث يشغل الهيكل الإجمالي 32 بايت.

دعونا نحاول تغيير ترتيب التصريح عن الحقول - سنرتبها بترتيب تصاعدي للحجم المشغول ونرى كيف تتغير الصورة في الذاكرة.



لا تأخذ بنيتنا 32 بايت ، ولكن 24. وفر 25٪.

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

دعونا ننتقل إلى StarData مرة أخرى. دعونا نحاول ترتيب حقولها من أجل زيادة الحجم المشغول.



أولاً ، Float و Int32 ، ثم Double and String. ليس هذا تتريس معقدة!
الخطوة التي تلقيناها هي 152 بايت. أي ، من خلال تحسين تنفيذ الخيارات والعمل مع المحاذاة ، تمكنا من تقليل حجم الهيكل من 208 إلى 152 بايت.

هل نقترب من حد إمكانات التحسين لدينا؟ ربما نعم. ومع ذلك ، هناك شيء آخر لم تجربه أنا وأنت - شيء ما هو أمر أكثر تعقيدًا ، ولكنه قد يذهلك أحيانًا بنتيجته.

محاسبة منطق المجال


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

ألق نظرة على StarData مرة أخرى. إن "عنق الزجاجة" الواضح لدينا هو حقول من النوع String ، فهي تشغل مساحة كبيرة حقًا. وهنا التفاصيل هي كما يلي: خلال وقت التشغيل ، تظل معظم هذه الأسطر فارغة! 146 نجمة فقط لها اسم "حقيقي" ، موضح في حقل الاسم الصحيح. و gl_id هو معرف النجم وفقًا لكتالوج Gliese ، الذي يحتوي على 3801 نجمة ، بعيدًا أيضًا عن المليون. سيتم تعيين bayer_flamstedt - تعيينات Flemstead - إلى النجوم 3064. النوع الطيفي النوع الطيفي هو 4307 ميل. اتضح أنه بالنسبة لمعظم النجوم ، ستكون متغيرات السلسلة المدخلة فارغة ، بينما تشغل 24 بايت لكل منها.

توصلت إلى مخرج التالي. دعونا نحصل على مصفوفة ترابطية كهيكل إضافي. كمفتاح - معرف رقمي فريد من نوع Int16 ، كقيمة ، اعتمادًا على وجود السلسلة المميزة - إما قيمتها أو -1.

في StarData المقابلة لـ rightName و gl_id و bayer_flamstedt و spectralType ، سنكتب الفهرس المقابل للمفتاح في الصفيف. إذا لزم الأمر ، احصل على سلسلة مميزة أو أخرى ، فسوف نطلب القيمة من الصفيف من خلال الفهرس. ليست هناك حاجة للقيام بذلك يدويًا - فنحن ننفذ بشكل أفضل وسيلة آمنة مناسبة:



فالأمر مهم جدًا هنا - فهو يخفي تعقيد التنفيذ الخاص بنا. يمكن تسجيل مصفوفة على أنها خاصة ، والآن ليس من الضروري معرفة وجودها.

بالطبع ، هذا الحل له ناقص. لا يمكن أن يؤثر حفظ الذاكرة إلا على حمل المعالج. مع هذا المخطط ، نحن مضطرون للوصول باستمرار إلى مجموعتنا النقابية ؛ وفي معظم الحالات - عبثا ، لأن معظم الأسطر ستبقى فارغة وستعود الطلبات "-1".

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

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

الإجمالي: خطوة الآن تعطينا 64 بايت!

هل هذا كل شيء؟ لا ، نحتاج الآن إلى التفكير في قواعد المحاذاة مرة أخرى: إعادة ترتيب الحقول من النوع Int16 أعلى.



الآن كل شيء. كما ترى ، باستخدام عدد صغير من الطرق البسيطة في الأساس ، تمكنا من تقليل حجم بنية StarData من 208 إلى 56 بايت. مليون نجمة تحتل الآن 500 ميغابايت ، ولكن 130. أقل أربع مرات!

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

سويفت مترجم الأمثل


معظم المبرمجين على دراية بألم إعادة تجميع مشروع طويل (لا يطاق). لقد أجريت تغييرًا بسيطًا على الرمز ، والآن استرخ وانتظر حتى انتهاء البناء.

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

أنا شخصياً بحثت عن التجميع في Xcode. كأداة ، استخدمت الأمر التالي:



يوجه هذا الأمر xCode لتتبع وقت الترجمة لكل وظيفة وكتابتها في ملف culprits.txt. يتم فرز محتويات الملف على طول الطريق.



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

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

ولكن هناك "لكن" واحد: لا يزال الرمز بدون أنواع أكثر قابلية للقراءة. وقد تحدثنا بالفعل عن الأولويات. لا تقم بالتحسين مسبقًا: في البداية ، ستكون قراءة التعليمات البرمجية أكثر تكلفة.

خيار الخادم


حتى الآن ، ذكرت طلبي فقط مع الواقع المعزز. ولكن بناءً على مليون نجم ، قمت أيضًا بإنشاء تطبيق خادم على Swift. ويمكنك ان ترى له النفس ، و رمز له على جيثب . هذه خدمة API تسمح لك بتلقي معلومات حول أي نجوم من قاعدة بياناتي الضخمة. تمكنت من تحسينه باستخدام نفس الأساليب التي استخدمتها للتطبيق على ARkit. أصبحت النتيجة في هذه الحالة ملموسة بشكل ملموس بالنسبة لي: خفض حجم الصوت إلى مستوى 500 ميجا بايت ، أتيحت لي الفرصة لوضعه على خادم Bluemix مجاني. ونتيجة لذلك ، تكلفني خدمتي مجانًا تمامًا.

لتلخيص


في الختام ، ملخص قصير عن الأفكار الرئيسية التي أردت مخاطبتك اليوم:

  • . . , , , ?
  • , unit-. , unit-. , . Unit- , .
  • . , . , : — .
  • العمل مع منطق المجال لتطبيقك. أقوى أداة تحسين هي العمل الماهر مع منطق المجال. تعرف على ميزات العمل ، وتفاصيل تطبيقك - حاول أن تأخذها في الاعتبار ، وابحث عن حلولك "الشخصية".
  • ذاكرة الوصول العشوائي مقابل وحدة المعالجة المركزية ابذل قصارى جهدك للحفاظ على توازن الذاكرة واستخدام المعالج. هذا دائمًا ما يمثل صعوبة كبيرة ، ولكن لا يزال من الممكن العثور على مثلى معين في كل حالة معينة.


إذا أعجبك هذا التقرير من مؤتمر Mobius ، يرجى ملاحظة أن Mobius 2018 Moscow سيعقد في 8-9 ديسمبر ، حيث سيكون هناك أيضًا الكثير من الأشياء المثيرة للاهتمام. منذ 1 نوفمبر ، ارتفعت أسعار التذاكر ، لذلك فمن المنطقي اتخاذ قرار الآن!

Source: https://habr.com/ru/post/ar427845/


All Articles