
سوف يعلمك هذا الكتاب كيفية زيادة أداء التعليمات البرمجية المدارة إلى الحد الأقصى ، من الناحية المثالية دون التضحية بأي من مزايا بيئة .NET ، أو في أسوأ الأحوال ، التضحية بعدد ضئيل منها. سوف تتعلم أساليب البرمجة المنطقية ، ومعرفة ما يجب تجنبه ، والأهم من ذلك ، والأهم من ذلك ، هو كيفية استخدام الأدوات المتاحة بحرية من أجل قياس مستوى الإنتاجية بسهولة. سوف تحتوي مواد التدريب على الحد الأدنى من الماء - فقط الأكثر ضرورة. يعطي الكتاب بالضبط ما تحتاج إلى معرفته ، إنه ذو صلة وموجز ، لا يحتوي على الكثير. تبدأ معظم الفصول بمعلومات عامة وخلفية ، متبوعة بنصائح محددة ، معدة مثل الوصفة ، والنهاية بقسم قياس وتصحيح الأخطاء خطوة بخطوة لمجموعة واسعة من السيناريوهات.
على طول الطريق ، سوف يندفع Ben Watson إلى مكونات محددة من بيئة .NET ، وعلى وجه الخصوص ، وقت تشغيل اللغة العامة (CLR) استنادًا إلى ذلك ، وسنرى كيف تتم إدارة ذاكرة جهازك ، وتوليد الشفرة ، وتنظيم التنفيذ متعدد الخيوط ، والكثير غير ذلك . سيظهر لك كيف تحدد بنية .NET في الوقت نفسه أداة البرنامج وتزودها بميزات إضافية وكيف يمكن أن يؤثر اختيار مسارات البرمجة بشكل كبير على الأداء الكلي للتطبيق. على سبيل المكافأة ، سيقوم المؤلف بمشاركة قصص من تجربة إنشاء أنظمة .NET كبيرة ومعقدة وعالية الأداء في Microsoft خلال السنوات التسع الماضية.
مقتطفات: اختيار حجم تجمع مؤشر الترابط المناسب
بمرور الوقت ، يتم تكوين تجمع مؤشرات الترابط بشكل مستقل ، ولكن في البداية لا يحتوي على محفوظات وسيبدأ في الحالة الأولية. إذا كان منتج البرنامج غير متزامن بشكل استثنائي ويستخدم معالجًا مركزيًا بشكل كبير ، فقد يعاني من تكاليف تشغيل مبدئية مرتفعة للغاية ، في انتظار إنشاء وتوافر المزيد من سلاسل العمليات. سيساعد ضبط معلمات بدء التشغيل على تحقيق حالة مستقرة بشكل أسرع بحيث يكون لديك ، منذ اللحظة التي يبدأ فيها التطبيق ، عدد معين من سلاسل العمليات الجاهزة:
const int MinWorkerThreads = 25; const int MinIoThreads = 25; ThreadPool.SetMinThreads(MinWorkerThreads, MinIoThreads);
كن حذرا هنا. عند استخدام كائنات المهام ، سيتم إرسالها استنادًا إلى عدد مؤشرات الترابط المتاحة لهذا. إذا كان هناك الكثير منها ، يمكن أن تخضع كائنات المهام لجدولة مفرطة ، مما سيؤدي على الأقل إلى انخفاض في كفاءة المعالج المركزي بسبب تبديل السياق بشكل متكرر. إذا لم يكن حجم العمل كبيرًا للغاية ، فيمكن لمجمع مؤشرات الترابط التبديل إلى استخدام خوارزمية يمكنها تقليل عدد مؤشرات الترابط ، مما يجعلها أقل من الرقم المحدد.
يمكنك أيضًا تعيين الحد الأقصى لعددهم باستخدام طريقة SetMaxThreads ، ولكن هذه التقنية عرضة لمخاطر مماثلة.
لمعرفة العدد المطلوب من مؤشرات الترابط ، اترك هذه المعلمة وحدها وحلل تطبيقك في حالة مستقرة باستخدام الأساليب ThreadPool.GetMaxThreads و ThreadPool.GetMinThreads أو عدادات الأداء التي تعرض عدد مؤشرات الترابط المشاركة في العملية.
لا تقاطع التدفقات
إن مقاطعة عمل الخيوط دون التنسيق مع عمل الخيوط الأخرى هو إجراء خطير إلى حد ما. يجب أن تنظف المجاري المائية نفسها ، ودعوتهم طريقة أجهض لا تسمح لهم إغلاق دون عواقب سلبية. عندما يتم إتلاف سلسلة عمليات ، تكون أجزاء التطبيق في حالة غير محددة. سيكون من الأفضل تعطل البرنامج ، ولكن من الناحية المثالية ، يلزم إعادة تشغيل نظيف.
لإنهاء سلسلة الرسائل بأمان ، تحتاج إلى استخدام نوع من الحالات المشتركة ، ويجب أن تحقق وظيفة سلسلة الرسائل نفسها هذه الحالة لتحديد متى يجب إكمالها. يجب تحقيق الأمن من خلال التماسك.
بشكل عام ، يجب عليك دائمًا استخدام كائنات المهام - لم يتم توفير واجهة برمجة التطبيقات لمقاطعة المهمة. لكي تتمكن من إنهاء سلسلة رسائل بشكل مستمر ، يجب ، كما ذكر سابقًا ، استخدام الرمز المميز CancellationToken.
لا تقم بتغيير أولوية الخيط
بشكل عام ، يعد تغيير أولوية مؤشرات الترابط مهمة فاشلة للغاية. في نظام Windows ، يتم تنفيذ عملية إرسال سلاسل الرسائل وفقًا لمستوى الأولوية. إذا كانت مؤشرات الترابط ذات الأولوية العالية جاهزة دائمًا للتشغيل ، فسيتم تجاهل مؤشرات الترابط ذات الأولوية المنخفضة ونادراً ما ستحصل على فرصة للتشغيل. بزيادة أولوية سلسلة الرسائل ، فإنك تقول إن عملها يجب أن يكون له الأسبقية على جميع الأعمال الأخرى ، بما في ذلك العمليات الأخرى. هذا ليس آمنا لنظام مستقر.
من الأفضل خفض أولوية الخيط إذا كان يعمل بشيء يمكنه الانتظار حتى الانتهاء من المهام ذات الأولوية العادية. قد يكون أحد الأسباب الجيدة لخفض أولوية مؤشر ترابط اكتشاف مؤشر ترابط خارج عن السيطرة تنفيذ حلقة لا نهائية. من المستحيل مقاطعة مؤشر ترابط بأمان ، وبالتالي فإن الطريقة الوحيدة لإرجاع مؤشر ترابط معين وموارد المعالج هي إعادة تشغيل العملية. إلى أن يصبح بالإمكان إغلاق الدفق والقيام به بشكل نظيف ، فإن تخفيض أولوية الدفق الخارج عن السيطرة سيكون طريقة معقولة لتقليل العواقب. تجدر الإشارة إلى أنه حتى مؤشرات الترابط ذات الأولوية الأقل ما زالت مضمونة للتشغيل مع مرور الوقت: كلما طال حرمانها من بدء التشغيل ، كلما تم تحديد الأولوية الديناميكية بواسطة Windows. الاستثناء هو أولوية الخمول THREAD_ - PRIORITY_IDLE ، حيث يقوم نظام التشغيل بجدولة مؤشر ترابط ليتم تنفيذه فقط عندما لا يكون لديه شيء آخر للبدء.
قد تكون هناك أسباب مبررة بشكل جيد لزيادة أولوية التدفق ، على سبيل المثال ، الحاجة إلى الاستجابة السريعة للمواقف النادرة. ولكن استخدام مثل هذه التقنيات يجب أن تكون حذرة للغاية. يتم تنفيذ عمليات إرسال سلاسل الرسائل في نظام التشغيل Windows بغض النظر عن العمليات التي ينتمون إليها ، لذلك سيتم تشغيل سلسلة رسائل ذات أولوية عالية من العملية الخاصة بك على حساب ليس فقط مؤشرات الترابط الأخرى الخاصة بك ، ولكن أيضًا كافة سلاسل العمليات من التطبيقات الأخرى التي تعمل على نظامك.
إذا تم استخدام تجمع مؤشرات الترابط ، فسيتم تجاهل أي تغييرات ذات أولوية في كل مرة يتم فيها إرجاع مؤشر ترابط إلى التجمع. إذا تابعت إدارة مؤشرات الترابط الأساسية عند استخدام مكتبة Parallel Task ، يجب أن تضع في اعتبارك أنه يمكن تشغيل العديد من المهام في نفس مؤشر الترابط قبل إعادتها إلى التجمع.
تزامن الصفحات والحظر
بمجرد أن تأتي المحادثة إلى عدة مؤشرات ترابط ، يصبح من الضروري مزامنتها. تتمثل المزامنة في توفير الوصول إلى مؤشر ترابط واحد فقط إلى حالة مشتركة ، على سبيل المثال ، إلى حقل فئة. عادةً ما تتم مزامنة مؤشرات الترابط باستخدام كائنات التزامن مثل Monitor و Semaphore و ManualResetEvent ، وما إلى ذلك. في بعض الأحيان يطلق عليها اسم locks ، وتسمى عملية المزامنة في سلسلة رسائل محددة lock.
إحدى الحقائق الأساسية حول الأقفال هي: فهي لا تزيد من الأداء أبدًا. في أفضل سيناريو - مع وجود التزامن الذي تم تنفيذه جيدًا وبدون منافسة - يمكن أن يكون الحظر محايدًا. يؤدي ذلك إلى إيقاف تنفيذ العمل المفيد بواسطة مؤشرات ترابط أخرى وإلى حقيقة أن وقت وحدة المعالجة المركزية يضيع ، ويزيد من وقت تبديل السياق ويسبب عواقب سلبية أخرى. يجب عليك تحمل هذا لأن الدقة أكثر أهمية من الأداء البسيط. ما إذا كان يتم احتساب النتيجة غير صحيحة بسرعة لا يهم!
قبل البدء في حل مشكلة استخدام جهاز القفل ، سننظر في المبادئ الأساسية.
هل أحتاج إلى الاهتمام بالأداء على الإطلاق؟
تبرير الحاجة إلى زيادة الإنتاجية أولاً. هذا يعيدنا إلى المبادئ التي نوقشت في الفصل 1. الأداء ليس بنفس القدر من الأهمية لجميع رمز التطبيق الخاص بك. لا يجب أن تخضع كل الشفرة لتحسين درجة n. وكقاعدة عامة ، يبدأ كل شيء بـ "الحلقة الداخلية" - الكود الذي يتم تنفيذه في معظم الأحيان أو الأكثر أهمية بالنسبة للأداء - وينتشر في جميع الاتجاهات حتى تتجاوز التكاليف الفوائد التي يتم تلقيها. هناك العديد من المناطق في التعليمات البرمجية التي تعتبر أقل أهمية بكثير من حيث الأداء. في مثل هذه الحالة ، إذا كنت بحاجة إلى قفل ، فقم بتطبيقه بهدوء.
والآن يجب أن تكون حذرا. إذا تم تنفيذ مقطع التعليمات البرمجية غير الحرج الخاص بك في سلسلة رسائل من تجمع مؤشرات الترابط وقمت بحظره لفترة طويلة ، فقد يبدأ تجمع مؤشرات الترابط في إدراج المزيد من مؤشرات الترابط لمعالجة الطلبات الأخرى. إذا كان هناك موضوع واحد أو خيطان يقومان بذلك من وقت لآخر ، فلا بأس بذلك. لكن إذا كان هناك الكثير من الخيوط التي تقوم بهذه الأشياء ، فقد تنشأ مشكلة ، ولهذا السبب ، فإن الموارد التي يجب أن تقوم بالعمل الحقيقي تنفق بلا فائدة. يمكن أن يؤدي عدم الاهتمام عند بدء تشغيل برنامج ذي حمل ثابت كبير إلى حدوث تأثير سلبي على النظام حتى من الأجزاء التي يكون الأداء العالي فيها غير مهم ، بسبب التبديل غير الضروري للسياق أو المشاركة غير المعقولة في تجمع مؤشرات الترابط. كما هو الحال في جميع الحالات الأخرى ، يجب إجراء قياسات لتقييم الوضع.
هل حقا بحاجة الى قفل؟
آلية قفل الأكثر فعالية هي تلك التي ليست كذلك. إذا استطعت التخلص تمامًا من الحاجة لمزامنة سلاسل الرسائل ، فستكون هذه هي أفضل طريقة للحصول على أداء عالي. هذا هو المثل الأعلى الذي ليس من السهل تحقيقه. عادةً ما يعني هذا أنك بحاجة إلى التأكد من عدم وجود حالة مشتركة قابلة للتغيير - يمكن معالجة كل طلب يمر من خلال التطبيق الخاص بك بشكل مستقل عن طلب آخر أو بعض البيانات القابلة للقراءة المركزية (قابلة للقراءة والكتابة). ستكون هذه الميزة أفضل سيناريو لتحقيق الأداء العالي.
وما زلت كن حذرا. من خلال إعادة الهيكلة ، من السهل الانتقال إلى الخارج وتحويل الشفرة إلى فوضى لا يستطيع أحد ، بمن فيهم أنت شخصًا ، اكتشاف ذلك. يجب ألا تذهب بعيدًا جدًا إلا إذا كانت الإنتاجية العالية حقًا عاملاً حاسمًا ولا يمكن تحقيق ذلك بطريقة أخرى. قم بتحويل الكود إلى غير متزامن ومستقل ، ولكن يبقى واضحًا.
في حالة قراءة عدة مؤشرات ترابط من متغير (وليس هناك تلميحات بالكتابة إليها من دفق) ، فلن تكون هناك حاجة إلى التزامن. جميع المواضيع يمكن أن يكون لها وصول غير محدود. ينطبق هذا تلقائيًا على الكائنات غير القابلة للتغيير مثل السلاسل أو قيم الأنواع غير القابلة للتغيير ، ولكن يمكن تطبيقه على أي نوع من الكائنات إذا كنت تضمن قابلية ثبات قيمتها أثناء القراءة بواسطة سلاسل رسائل متعددة.
إذا كان هناك العديد من مؤشرات الترابط التي تكتب إلى بعض المتغيرات المشتركة ، معرفة ما إذا كان يمكن القضاء على الوصول المتزامن من خلال الانتقال إلى استخدام متغير محلي. إذا تمكنت من إنشاء نسخة مؤقتة للعمل ، فستختفي الحاجة إلى المزامنة. هذا مهم بشكل خاص للوصول المتزامن المتزامن. من إعادة الوصول إلى المتغير المشترك ، تحتاج إلى الانتقال إلى إعادة الوصول إلى المتغير المحلي بعد الوصول لمرة واحدة إلى المتغير المشترك ، كما في المثال البسيط التالي لإضافة عناصر إلى مجموعة مشتركة بواسطة عدة مؤشرات ترابط.
object syncObj = new object(); var masterList = new List<long >(); const int NumTasks = 8; Task[] tasks = new Task[NumTasks]; for (int i = 0; i < NumTasks; i++) { tasks[i] = Task.Run(()=> { for (int j = 0; j < 5000000; j++) { lock (syncObj) { masterList.Add(j); } } }); } Task.WaitAll(tasks);
يمكن تحويل هذا الرمز على النحو التالي:
object syncObj = new object(); var masterList = new List<long >(); const int NumTasks = 8; Task[] tasks = new Task[NumTasks]; for (int i = 0; i < NumTasks; i++) { tasks[i] = Task.Run(()=> { var localList = new List<long >(); for (int j = 0; j < 5000000; j++) { localList.Add(j); } lock (syncObj) { masterList.AddRange(localList); } }); } Task.WaitAll(tasks);
على الجهاز الخاص بي ، يعمل الإصدار الثاني من الشفرة بأكثر من ضعف سرعة تشغيل الإصدار الأول.
في نهاية المطاف ، فإن الدولة المشتركة القابلة للتغيير هي عدو أساسي للأداء. يتطلب تزامنًا لأمان البيانات ، مما يؤدي إلى انخفاض الأداء. إذا كان التصميم الخاص بك لديه على الأقل أدنى فرصة لتجنب الحجب ، فأنت على وشك تنفيذ نظام مثالي متعدد الخيوط.
ترتيب تفضيلات المزامنة
عند تحديد ما إذا كان أي نوع من التزامن ضروريًا ، يجب أن يكون مفهوما أنه ليس كلهم لديهم نفس الأداء أو خصائص السلوك. في معظم الحالات ، تحتاج فقط إلى استخدام قفل ، وعادة ما يكون هذا هو الخيار الأصلي. يتطلب استخدام شيء آخر غير الحظر ، من أجل تبرير التعقيد الإضافي ، قياسات مكثفة. بشكل عام ، نحن نعتبر آليات المزامنة بالترتيب التالي.
1. شاشة قفل / فئة - تحافظ على بساطة الشفرة وشمولها وتوفر توازنًا جيدًا في الأداء.
2. النقص التام في التزامن. تخلص من الحالات المشتركة القابلة للتغيير ، وإعادة الهيكلة وتحسينها. يعد هذا الأمر أكثر صعوبة ، ولكن إذا نجح ، فسيعمل بشكل أفضل بشكل أساسي من تطبيق الحظر (إلا في حالة حدوث أخطاء أو تدهور البنية).
3. الطرق البسيطة المتشابكة للتشابك - في بعض السيناريوهات ، قد تكون أكثر ملاءمة ، ولكن بمجرد أن يصبح الموقف أكثر تعقيدًا ، تابع استخدام قفل القفل.
وأخيرًا ، إذا كنت تستطيع حقًا إثبات فوائد استخدامها ، فاستخدم الأقفال المعقدة الأكثر تعقيدًا (ضع في اعتبارك: نادراً ما تكون مفيدة بقدر ما تتوقع):
- الأقفال غير المتزامنة (ستناقش لاحقًا في هذا الفصل) ؛
- كل شخص اخر
قد تملي ظروف معينة أو تعوق استخدام بعض هذه التقنيات. على سبيل المثال ، من غير المرجح أن يتفوق دمج أساليب Interlocked متعددة على عبارة تأمين واحدة.
»يمكن الاطلاع على مزيد من المعلومات حول الكتاب على
موقع الناشر»
المحتويات»
مقتطفاتخصم 25٪ على كوبون الباعة المتجولين -
.NETعند دفع النسخة الورقية من الكتاب ، يتم إرسال كتاب إلكتروني عبر البريد الإلكتروني.