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

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



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

  • ماذا يمكن أن يتوقع البرنامج؟
  • ملفات تعريف استخدام الموارد التي ليست موارد وحدة المعالجة المركزية.
  • ملفات تعريف سياق التبديل غير المقصود.

ماذا يتوقع البرنامج؟


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

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

كيف يمكن العثور على أماكن البرامج التي يحدث فيها شيء ما يؤثر بشدة على الأداء؟

الطريقة الأولى: تحليل الوقت الذي لا يستخدم فيه البرنامج المعالج


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

يمكن لنظام التشغيل أن يخبرنا بالضبط مقدار وقت المعالج المستخدم.

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

أولاً ، قم بإنشاء جهاز ضبط وقت لقياس المهلة:

 import os def not_cpu_time():    times = os.times()    return times.elapsed - (times.system + times.user) 

ثم قم بإنشاء ملف تعريف يحلل هذه المرة:

 import cProfile, pstats def profile_not_cpu_time(f, *args, **kwargs):    prof = cProfile.Profile(not_cpu_time)    prof.runcall(f, *args, **kwargs)    result = pstats.Stats(prof)    result.sort_stats("time")    result.print_stats() 

بعد ذلك ، يمكنك تحديد وظائف مختلفة:

 >>> profile_not_cpu_time( ...   lambda: urlopen("https://pythonspeed.com").read()) ncalls tottime percall filename:lineno(function)    3  0.050  0.017 _ssl._SSLSocket.read    1  0.040  0.040 _socket.getaddrinfo    1  0.020  0.020 _socket.socket.connect    1  0.010  0.010 _ssl._SSLSocket.do_handshake  342  0.010  0.000 find.str  192  0.010  0.000 append.list 

تتيح لنا النتائج أن نستنتج أن معظم الوقت قد قضى في قراءة البيانات من المقبس ، لكن الأمر استغرق بعض الوقت لإجراء عمليات بحث DNS ( getaddrinfo ) ، وكذلك لإجراء مصافحة TCP ( connect ) ومصافحة TLS / SSL.

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

لماذا يوجد وقت مسجل لـ str.find و list.append ؟ عند إجراء مثل هذه العمليات ، لا يوجد شيء ينتظره البرنامج ، لذلك يبدو التفسير معقولًا ، والذي نتعامل معه مع موقف لم تتم فيه العملية بالكامل. ربما - في انتظار إكمال عملية أخرى ، أو في انتظار استكمال تحميل البيانات في الذاكرة من ملف المبادلة. يشير هذا إلى أنه تم قضاء بعض الوقت في تنفيذ هذه العمليات ، وهو ليس جزءًا من وقت المعالج.

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

الطريقة رقم 2: تحليل عدد مفاتيح تبديل السياق المتعمد


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

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

يمكن أن تتوقف العملية عن استخدام موارد المعالج لسببين:

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

سنقوم بملف تعريف مفاتيح السياق المتعمد.

دعنا نكتب منشئ ملفات التعريف الذي يعد مفاتيح تبديل السياق المتعمد باستخدام مكتبة psutil :

 import psutil _current_process = psutil.Process() def profile_voluntary_switches(f, *args, **kwargs):    prof = cProfile.Profile(        lambda: _current_process.num_ctx_switches().voluntary)    prof.runcall(f, *args, **kwargs)    result = pstats.Stats(prof)    result.sort_stats("time")    result.print_stats() 

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

 >>> profile_voluntary_switches( ...   lambda: urlopen("https://pythonspeed.com").read()) ncalls tottime percall filename:lineno(function)     3  7.000  2.333 _ssl._SSLSocket.read     1  2.000  2.000 _ssl._SSLSocket.do_handshake     1  2.000  2.000 _socket.getaddrinfo     1  1.000  1.000 _ssl._SSLContext.set_default_verify_path     1  1.000  1.000 _socket.socket.connect 

الآن ، بدلاً من بيانات وقت الانتظار ، يمكننا أن نرى معلومات حول عدد مفاتيح تبديل السياق المقصودة التي حدثت.

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

النتائج


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

بشكل عام ، تجدر الإشارة إلى أن أي مؤشرات قابلة للقياس تتعلق بعمل البرنامج تتناسب مع التنميط. على سبيل المثال ، ما يلي:

  • عدد القراءات ( psutil.Process().read_count ) ويكتب ( psutil.Process().write_count ).
  • على نظام Linux ، إجمالي عدد البايتات للقراءة Process().read_chars (psutil. Process().read_chars ).
  • تتطلب مؤشرات تخصيص الذاكرة (إجراء مثل هذا التحليل بعض الجهد ؛ ويمكن القيام بذلك باستخدام jemalloc ).

يمكن الاطلاع على تفاصيل أول عنصرين من هذه القائمة في وثائق psutil .

أعزائي القراء! كيف يمكنك أن تصفح تطبيقات بيثون الخاصة بك؟

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


All Articles