الاستخدام الفعال لل libdispatch

( ملاحظة: مؤلف المادة الأصلية هو Thomastclementdev ، مستخدم لـ github و twitter . يتم حفظ سرد أول شخص يستخدمه المؤلف في الترجمة أدناه. )

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


لديه أيضًا العديد من التغريدات حول هذا الموضوع:


من صنعي:

  • يجب أن يحتوي البرنامج على عدد قليل جدًا من قوائم الانتظار باستخدام تجمع عمومي ( مؤشرات الترابط - تقريباً لكل. ). إذا كانت كل قوائم الانتظار هذه نشطة في نفس الوقت ، فستتلقى نفس عدد سلاسل العمليات قيد التشغيل في وقت واحد. يجب اعتبار قوائم الانتظار هذه بمثابة سياقات تنفيذ في البرنامج (واجهة المستخدم الرسومية ، التخزين ، العمل في الخلفية ، ...) التي تستفيد من التنفيذ المتزامن.
  • ابدأ بالتنفيذ المتسلسل. عندما تكتشف مشكلة في الأداء ، خذ قياسات لاكتشاف السبب. وإذا كان التنفيذ المتوازي يساعد ، استخدمه بعناية. تحقق دائمًا من أن الشفرة الموازية تعمل تحت ضغط النظام. بشكل افتراضي ، أعد استخدام قوائم الانتظار. أضف خطوطًا عندما تجلب فوائد قابلة للقياس. يجب ألا تستخدم معظم التطبيقات أكثر من ثلاثة إلى أربعة قوائم انتظار.
  • قوائم الانتظار التي تحتوي على قائمة انتظار أخرى كهدف تعمل بشكل جيد وحجم.
    ( لاحظ perev.: حول تعيين قائمة انتظار كهدف لقائمة انتظار أخرى يمكن قراءتها ، على سبيل المثال ، هنا . )
  • لا تستخدم dispatch_get_global_queue (). هذا لا يتوافق مع جودة الخدمة والأولويات ويمكن أن يؤدي إلى نمو هائل في عدد التدفقات. بدلاً من ذلك ، قم بتشغيل التعليمات البرمجية الخاصة بك في أحد سياقات التنفيذ الخاصة بك.
  • يعتبر dispatch_async () مضيعة للموارد الخاصة بالكتل القابلة للتنفيذ الصغيرة (أقل من 1 مللي ثانية) ، لأن هذه الدعوة ستتطلب على الأرجح إنشاء سلسلة رسائل جديدة بسبب الحماسة المفرطة في libdispatch. بدلاً من تبديل سياق التنفيذ لحماية الحالة المشتركة ، استخدم آليات القفل للوصول إلى الحالة المشتركة في نفس الوقت.
  • بعض الفئات / المكتبات مصممة بشكل جيد من حيث أنها تعيد استخدام سياق التنفيذ الذي يمرره رمز الاتصال. هذا يسمح باستخدام القفل التقليدي لضمان سلامة الخيط. عادةً ما يكون os_unfair_lock أسرع آلية تأمين في النظام: إنها تعمل بشكل أفضل مع الأولويات وتتسبب في تبديل أقل للسياق.
  • في حالة التنفيذ الموازي ، يجب ألا تتعارض مهامك فيما بينها ، وإلا تنخفض الإنتاجية بشكل حاد. القتال يأخذ أشكالا عديدة. الحالة الواضحة: الكفاح من أجل القبض على القفل. ولكن في الواقع ، فإن هذا الصراع لا يعني أكثر من استخدام مورد مشترك ، والذي يصبح عنق الزجاجة: IPC (اتصال بين العمليات) / شياطين OS ، malloc (حظر) ، ذاكرة مشتركة ، I / O.
  • لا تحتاج إلى تنفيذ جميع التعليمات البرمجية بشكل غير متزامن من أجل تجنب حدوث زيادة هائلة في عدد مؤشرات الترابط. من الأفضل استخدام عدد محدود من قوائم الانتظار الأدنى ورفض استخدام dispatch_get_global_queue ().
    ( ملاحظة perev. 1: على ما يبدو ، هذه هي الحالة التي تحدث فيها زيادة هائلة في عدد مؤشرات الترابط عند مزامنة عدد كبير من المهام المتوازية "إذا كان لدي الكثير من القطع وكلهم يريدون الانتظار ، يمكننا الحصول على ما نسميه بموضوع انفجار. " )
    ( ملاحظة ص 2: من خلال المناقشة ، يمكن فهم أن بيير هابوزيت يعني قوائم الانتظار الأدنى "المعروفة للنواة عندما يكون لديهم مهام" . هنا نتحدث عن نواة نظام التشغيل. )
  • يجب ألا ننسى التعقيد والأخطاء التي تنشأ في بنية مليئة بالتنفيذ غير المتزامن وعمليات الاسترجاعات. التعليمات البرمجية القابلة للتسلسل لا تزال أسهل بكثير في القراءة والكتابة والمحافظة عليها.
  • قوائم الانتظار التنافسية أقل تحسينًا من قوائم الانتظار المتسلسلة. استخدمها إذا كنت تقيس مكاسب الأداء ، وإلا فهو تحسين سابق لأوانه.
  • إذا كنت بحاجة إلى إرسال مهام في قائمة انتظار واحدة بشكل غير متزامن ومتزامن ، فبدلاً من dispatch_sync () استخدم dispatch_async_and_wait (). لا تضمن dispatch_async_and_wait () التنفيذ على سلسلة الرسائل التي نشأت منها المكالمة ، مما يقلل من تبديل السياق عندما تكون قائمة انتظار الهدف نشطة.
    ( ملاحظة الترجمة رقم 1: في الواقع ، لا تضمن dispatch_sync () أيضًا ، تنص الوثائق المتعلقة به فقط على "تنفيذ كتلة على سلسلة الرسائل الحالية ، كلما كان ذلك ممكنًا. مع استثناء واحد: يتم تنفيذ الكتلة المرسلة إلى قائمة الانتظار الرئيسية دائمًا في سلسلة الرسائل الرئيسية. " )
    ( ملاحظة الترجمة. 2: حول dispatch_async_and_wait () في الوثائق وفي الكود المصدري )
  • صحيح باستخدام 3-4 النوى ليست بهذه البساطة. معظم الذين يحاولون ، في الواقع ، لا يمكن التعامل مع التوسع وهدر الطاقة من أجل زيادة طفيفة في الإنتاجية. كيف تعمل معالجات المحموم لن يساعد. على سبيل المثال ، ستقوم Intel بتعطيل Turbo-Boost إذا تم استخدام عدد كافٍ من النوى.
  • قياس أداء المنتج الخاص بك في العالم الحقيقي للتأكد من جعله أسرع وليس أبطأ. كن حذرًا في اختبارات الأداء الجزئي - فهي تخفي تأثير ذاكرة التخزين المؤقت وتبقي تجمع مؤشرات الترابط ساخنًا. للتحقق مما تفعله ، يجب أن يكون لديك دائمًا اختبار ماكرو.
  • libdispatch فعال ، لكن لا توجد معجزات. الموارد ليست بلا نهاية. لا يمكنك تجاهل حقيقة نظام التشغيل والأجهزة التي يتم تنفيذ الرمز عليها. أيضا ، ليس كل رمز متوازنة بشكل جيد.

ألقِ نظرة على جميع مكالمات dispatch_async () في الكود واسأل نفسك: هل المهمة التي ترسلها مع هذه المكالمة تستحق بالفعل تبديل السياق. في معظم الحالات ، قد يكون القفل هو الخيار الأفضل.

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

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

المزيد من الروابط


يجب أن يحتوي البرنامج على عدد قليل جدًا من قوائم الانتظار باستخدام التجمع العالمي


( ملاحظة perev.: قراءة الرابط الأخير ، لا يمكن أن تقاوم ونقل قطعة من منتصف مراسلات بيير Habuzit مع كريس لوتنر. أدناه هي واحدة من إجابات بيير Habuzit في 039420.html )
<...>
أدرك أنه من الصعب بالنسبة لي أن أنقل وجهة نظري ، لأنني لست شابًا في هندسة اللغة ، فأنا شاب في بنية النظام. وبالتأكيد لا أفهم الممثلين بما يكفي لتحديد كيفية دمجهم في نظام التشغيل. لكن بالنسبة لي ، بالعودة إلى مثال قاعدة البيانات ، تختلف قاعدة بيانات الفاعل أو واجهة الفاعل للشبكة من المراسلات السابقة ، على سبيل المثال ، عن استعلام SQL هذا أو استعلام الشبكة هذا. الأول هو الكيانات التي يجب أن يعرفها نظام التشغيل في النواة. في حين أن استعلام SQL أو استعلام شبكة ما هي مجرد عناصر في قائمة الانتظار للتنفيذ في المقام الأول. بمعنى آخر ، تختلف الجهات الفاعلة ذات المستوى الأعلى هذه لأنها أعلى مستوى ، مباشرة أعلى وقت تشغيل kernel / low-level. وهذا هو الجوهر الذي يجب أن يكون جوهره قادرًا على التفكير فيه. هذا يجعلهم رائعين.

هناك نوعان من قوائم الانتظار ومستوى واجهة برمجة التطبيقات المقابلة في مكتبة الإرسال:
  • قوائم الانتظار العالمية التي ليست قوائم انتظار مثل الآخرين. وفي الواقع ، فهي مجرد تجريد على تجمع الخيط.
  • جميع قوائم الانتظار الأخرى التي يمكنك تعيين هدف واحد للآخر كما تريد.

اليوم أصبح من الواضح أن هذا كان خطأ وأنه يجب أن يكون هناك 3 أنواع من قوائم الانتظار:

  • قوائم الانتظار العالمية ، والتي ليست قوائم انتظار حقيقية ، ولكنها تمثل عائلة النظام التي تتطلب سياق التنفيذ الخاص بك (الأولويات بشكل أساسي). ويجب أن نحظر إرسال المهام مباشرة إلى هذه الطوابير.
  • قوائم الانتظار الأدنى (التي تتبعها GCD في السنوات الأخيرة وتدعو "القواعد" في الكود المصدري ( يبدو أن الكود المصدري لـ GCD نفسه يشير إلى - الترجمة التقريبية. ). تُعرف قوائم الانتظار الأدنى للنواة عندما يكون لديهم مهام.
  • أي قوائم انتظار "داخلية" أخرى لا تعرفها النواة على الإطلاق.

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

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


ابدأ بالتنفيذ المتسلسل.


لا تستخدم قوائم الانتظار العالمية


احذر من الخطوط التنافسية


لا تستخدم مكالمات المزامنة لحماية الحالة المشتركة


لا تستخدم دعوات المزامنة للمهام الصغيرة


يجب أن تكون بعض الفئات / المكتبات متزامنة


صراع المهام الموازية فيما بينها هو قاتل الإنتاجية


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


لا تستخدم الإشارات في انتظار مهمة غير متزامنة


يحتوي NSOperation API على بعض الأخطاء الخطيرة التي يمكن أن تؤدي إلى تدهور الأداء.


تجنب اختبارات الأداء الجزئي


الموارد ليست غير محدودة


حول dispatch_async_and_wait ()


باستخدام 3-4 النوى ليست سهلة


تم تحقيق العديد من التحسينات في الأداء في نظام التشغيل iOS 12 باستخدام الشياطين ذات الخيوط المفردة

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


All Articles