How to Choose The Idea Threadpool Size

في هذا المقال سنتناول بشكل بسيط وسهل الإجابة عن هذا السؤال. وستعرفون أن أهم العوامل التي تؤثر في اختيارك لحجم الـ Thread Pool بشكل أساسي هم.
How to Choose The Idea Threadpool Size
How to Choose The Idea Threadpool Size

في هذه الصفحة

ما هو الحجم الأمثل والمناسب للـ Thread Pool في المهمة التي أود القيام بها ؟

كان يتردد هذا السؤال علي كثيرًا أثناء عملي في مختلف الأنواع من المهام كمعالجة تدفق البيانات أو ما يعرف بالـ Data Stream Processing أو تنفيذ بعض المهام بشكل متوازٍ (في نفس الوقت) للتحسين من أداء تنفيذها. وهو ما يعرف بالـ Parallel Processing. وأكاد أجزم أن هنالك الكثير ممن راودهم هذا السؤال أثناء عملهم في مختلف المهام

في هذا المقال سنتناول بشكل بسيط وسهل الإجابة عن هذا السؤال. وستعرفون أن أهم العوامل التي تؤثر في اختيارك لحجم الـ Thread Pool بشكل أساسي هم:

١- عدد الـ CPU Cores التي يستطيع ويُسمَح للبرنامج الخاص بك استعمالها والحصول عليها.

٢- طبيعة المهمة التي تريد القيام بها إذا ما كانت مهمة تعتمد بشكل كبير على المدخلات والمخرجات أو ما يعرف بـ I/O Bound Task، أم إذا كانت مهمة تعتمد بشكل قوي على المعالجات وهو ما يعرف بالـ CPU Intensive Task.

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

ما هو الحجم المثالي للـ Thread Pool ؟

ليس هناك حجم مثالي للـ Thread Pool؛ فالأمر كله يعتمد على نوع المهمة التي تود القيام بها بالإضافة الى بعض المقايضات Trade-Offs التي عليك أن تفاضل بينها.

ما هي الـ CPU CORES ؟

الـ CPU Cores هي تمثل الأنوية التي تتواجد داخل وحدة المعالجة المركزية أو ما يعرف بالـCPU، وحيث إنه منذ قديم الزمن كانت هذه الوحدة تضم فقط نواة واحدة، فلم يكن باستطاعتها إلا أن تقوم بعمل مهمة واحدة فقط في كل مرة. أما الآن فنحن نسمع عن العديد من الأنوية Multi-Core CPU. وبهذا الشكل أصبح باستطاعتنا الاستفادة من عمل أكثر من مهمة في كل مرة بدلًا من مهمة واحدة. ومن هنا نستطيع الاعتماد على تلك الـ Cores في تسريع وتحسين الأداء عن طريق الاستفادة من الـ Parallelism.

هذه الصورة تمثل الفرق بين الـ Single Core والـQuad Core:

ما هو الفرق بين الـ I/O Bound Tasks والـ CPU Intensive Tasks ؟

كما علمنا هنالك نوعان من المهام التي يجب وضعها في الاعتبار عند تحديد حجم الـ Thread Pool وهم ال I/O Bound Tasks والـCPU Intensive Tasks… ومن اسمهما يمكننا أن نستنتج أن مهام الـ I/O هي المهام التي تعتمد بشكل أساسي على انتظار عمليات المدخلات والمخرجات حتى تنتهي. على سبيل المثال: القراءة والكتابة على القرص الصلب أو عمليات القراءة والكتابة في قواعد البيانات وآخيرًا وليس آخرًا العمليات التي ترتبط بالشبكات أو ما يعرف بالـ Network Operations.

أما على الجانب الآخر فمهام الـ CPU Intensive بكل بساطة هي عمليات تعتمد بشكل أساسي على الـ CPU. على سبيل المثال: انتظار العمليات الحسابية الصعبة والخوارزميات المعقدة حتى تنتهي من عملها مثل: عمليات البحث والترتيب في عدد كبير من البيانات أو الحسابات الرياضية المعقدة.

ما الذي نسعى إليه وما هو الهدف الذي نود الوصول إليه باختيار حجم الـ Thread Pool؟

نحن نسعى ونهدف إلى تحقيق أقصى استفادة ممكنة من الـ CPU والـ Resources التي نتعامل معها دون إهدارها.

مهام وحدة البيانات المكثفة أو ما يعرف بالـ CPU Intensive ؟

لنفترض أننا نتعامل مع Single CPU Core وبالتالي لن يكون معنا غير Thread أساسي واحد قادر على عمل مهمة واحدة فقط خلال فترة من الزمن.

في الشكل السابق يمكننا أن نرى أنه خلال فترة زمنية معينة كان هذا هو شكل الـ CPU وهو يقوم بتنفيذ المهام… خلال تلك الفترة، فهناك فترات من الزمن يستقبل وينفذ المهام وفترات أخرى يكون خاملًا لا يقوم بتنفيذ أية مهام.

ولكن ماذا لو أصبح عدد المهام التي يجب على الـ CPU إتمامها مهمتان بدلًا من مهمة واحدة كما في الشكل السابق ؟ سيكون الشكل كما هو موضح:

يمكننا الآن ملاحظة شيء مهم وهو أننا استطعنا أن نقلل الفجوة عن الشكل الأول وهو عدم ترك الـ CPU ينتظر بدون عمل أية مهام. ماذا لو أصبح لدينا عدد المهام من هذه النوعية ١٠٠ (مائة) بدلًا من مهمتان ؟ ومازال لدينا Single Core CPU ومازلنا نعتمد على One Thread فقط لإتمام تلك المهام ؟

يمكننا الآن أن نرى Thread 1 الذي يعمل على الـ Single Core CPU يقوم باستقبال وتنفيذ المهام بشكل متتابع مهمة تلو الآخرى حيث أنه يقوم بالانتهاء من الأولى ثم تليها الثانية وبالمثل حتى يصل للانتهاء من جميع المهام المطلوبة. وهنا اذا فكرنا مليًا يمكننا إدراك شيء مهم وهو أننا حققنا الهدف المرجو وهو تحقيق الاستفادة القصوى من استخدام الـ CPU.

الآن دعونا نفترض أنه بدلًا من وجود Thread واحد فقط لدينا في الـ Thread Pool , يوجد الآن 2 Threads وعدد المهام المطلوبة كما في المثال السابق ١٠٠ (مائة).

ما الذي حدث هنا ؟

يمكننا الآن بالنظر رؤية أنه تم تحقيق المرجو من الهدف وهو أقصى استفادة ممكنة من استخدام الـ CPU ولكن هنالك أمر مهم يجب معرفته هنا… وهو لأننا نعتمد على Single Core CPU فسيكون هنالك Thread واحد فقط يعمل خلال فترة من الزمن… وما المشكلة في أمر كهذا؟

المشكلة تكمن في الآتي: سيقوم نظام التشغيل بجدولة الـ Threads 2 لكي يأخذ مكان Thread في تنفيذ المهام , ومن الممكن هنا أن يتم ذلك قبل انتهاء Thread 1 من إتمام المهمة التي كان ينفذها سابقًا ، وهذا ما يعرف بالـ Time Slicing ورغم أن Thread 2 سيقوم باستكمال المهمة التي كان يقوم بها Thread 1 إلا أن… هذه الجدولة والتبادل المستمر بين Thread 1 و Thread 2 لم تقم بإضافة أية فائدة تذكر… بل بالعكس قد تكون سببًا كبيرًا في إهدار الموارد وإهدار الـ CPU. وهذا لأن عملية بناء Thread ليست عملية هينة… والتبادل الذي يتم لكي يأخذ كل Thread دوره ويستكمل المهام ليست عملية سهلة. وهذا التبادل من الأمور الشهيرة والمعروفة والتي تعرف بالـContext Switching ..

 

دعونا الآن نرى الحالة الآخيرة في هذا النوع من المهام ولنفترض أنه لدينا Dual Core CPU بدلًا من Single Core CPU ونستنتج ما هو الحجم المناسب والملائم للـ Thread Pool في حالة الـ CPU Intensive Tasks.

الآن باستطاعتنا إدراك أمر هام ألا وهو: بغض النظر عن عدد الـ Threads , إذا كنا نتعامل مع نوعية الـ CPU Intensive Tasks فنحن محدودون بشيء في غاية الأهمية وهو عدد الـ Cores التي يحتوي عليها النظام ومتاحة لك.

والعلاقة التي يمكننا الاعتماد عليها هي: كلما زاد عدد ال Cores كلما كان الاعتماد على الـ parallelization أفضل وهذا لأن وجود أكثر من Core يضمن لنا تنفيذ أكثر من مهمة في نفس الوقت بشكل متوازٍ.

ولكن علينا والحذر من شيء مهم وهو: إذا كان عدد الـ Threads أكبر من الـCPU Cores فهذا قد يؤدي بك إلى مشاكل وخصوصًا في الأداء وفي ذاكرة التطبيق الذي تعتمد عليه. كما أنه لن يكون له أي فائدة تذكر كما رأينا سابقًا في مثال الـ Single Core والـ 2 Threads ..

ماهو حجم الـ Thread Pool المناسب للـ CPU Intensive Tasks ؟

الإجابة هي: نفس عدد الـ Cores التي يمكنك الحصول على Access عليها في التطبيق.

مهام المدخلات والمخرجات أو ما يعرف بالـ I/O Bound Tasks ؟

لنفترض أننا نتعامل مع Single CPU Core وبالتالي لن يكون معنا غير Thread أساسي واحد قادر على عمل مهمة واحدة فقط خلال فترة من الزمن. ولكن الفرق هذه المرة هو نوع المهمة التي سنتعامل معها فهي من النوع I/O Bound.

مازال هدفنا الأساسي من اختيار الحجم الأمثل للـ Thread Pool هو تحقيق أقصى استفادة ممكنة من الـ CPU.

يمكنك الآن رؤية أن الـ Thread المسئول عن تنفيذ المهمة مشغول بسبب انتظاره لعملية الـ I/O كي تتم بنجاح، وهذا ليس فعالا على الإطلاق ولا يحقق الهدف الذي نسعى إليه في الاستفادة من الـ CPU.

وحتى نصل إلى تحقيق هدفنا فلا بد من استغلال الفترة أو الفجوة الزمنية التي يتوقف عندها الـ Thread لانتظار عملية الـ I/O من الانتهاء.

فكما نرى في الصورة السابقة يقوم نظام التشغيل بجدولة Threads أخرى لتعمل حتى تنتهي عملية الـ I/O ويقوم باسترجاع الـ Thread المنتظر لاستكمال المهمة التي كان موكل للقيام بها وتنفيذها.

لذا يمكننا الاستنتاج أنه في مهام الـ I/O حتى إذا كان الـ CPU Single Core فلدينا فرصة لتحسين الأداء وتحقيق أقصى استفادة من الـ CPU وذلك عن طريق تشغيل أكثر من Thread مختلف لأن نظام التشغيل سيقوم بجدولتهم ليقوموا بمهام أخرى حتى تنتهي عمليات الـ I/O.

زيادة عدد الـ Threads في مهام الـ I/O Bound يحسن ويسرع من الأداء ويحقق أقصى استفادة ممكنة من الـ CPU حيث إنه يقوم باستغلال الوقت الذي ينتظره الـ Thread حتى تنتهي عملية الـI/O.

ماهو حجم الـ Thread Pool المناسب للـ I/O Bound Tasks ؟

الإجابة هي: يمكنك اضافة الـ Threads وتحديد ذلك بالوضع في الاعتبار الفترة الزمنية التي تحتاجها مهمة الـ I/O للانتهاء.

هل يوجد علاقة رياضية يمكن الاعتماد عليها ؟

بالطبع يوجد علاقة رياضية عامة وشهيرة ويمكن الاعتماد عليها في تحديد حجم الـ Thread Pool وهي:

الحجم الأمثل للـ Thread Pool = عدد الـ CPU Cores * ( ١ + (وقت الانتظار / وقت الـ CPU) )

وبالتالي في حالة CPU Intensive Tasks سيكون وقت الانتظار = صفر فيكون الحجم الأمثل هو نفس عدد الـ Cores كما استنتجنا سابقًا، وفي حالة الـ I/O Bound Tasks يكون وقت الانتظار قيمة ليست بصفر وبالتالي سيكون الحجم أكبر من أو يساوي عدد الـ Cores

اشترك الآن بنشرة اقرأ-تِك الأسبوعية

لا تدع أي شيء يفوتك. واحصل على أحدث المقالات المميزة مباشرة إلى بريدك الإلكتروني وبشكل مجاني!