
نشارك اليوم معك ترجمة لمقال من مُنشئ FunctionTrace ، وهو ملف تعريف Python بواجهة رسومية سهلة الاستخدام يمكنها وضع ملف تعريف للمعالجات المتعددة والتطبيقات متعددة مؤشرات الترابط واستخدام موارد أقل بكثير من أدوات تحليل Python الأخرى. لا يهم إذا كنت تتعلم تطوير الويب في Python أو كنت تستخدمه لفترة طويلة - من الجيد دائمًا فهم ما تقوم به التعليمات البرمجية الخاصة بك. حول كيفية ظهور هذا المشروع ، وحول تفاصيل تطويره - مزيد من الخفض.
المقدمة
كان Firefox Profiler هو حجر الزاوية في Firefox خلال عصر Project Quantum . عندما تفتح إدخال المثال ، سترى واجهة تحليل أداء قوية تستند إلى الويب تتضمن أشجار الاستدعاءات ومخططات المكدس ومخططات إطلاق النار والمزيد. يتم حفظ جميع إجراءات تصفية البيانات وقياسها وتقطيعها وتحويلها في عنوان URL يمكن مشاركته. يمكن مشاركة النتائج في تقرير خطأ ، ويمكن توثيق نتائجك ، ومقارنتها بالسجلات الأخرى ، أو يمكن نقل المعلومات لمزيد من البحث. يحتوي Firefox DevEdition على سلسلة ملفات تعريف مضمنة . هذا التدفق يجعل التواصل سهلاً. هدفنا هو تمكين جميع المطورين ، حتى خارج Firefox ، من التعاون بشكل منتج.
سابقًا ، استورد Firefox Profiler تنسيقات أخرى بدءًا من Linux perf و Chrome . بمرور الوقت ، أضاف المطورون المزيد من التنسيقات. تظهر المشاريع الأولى لتكييف Firefox لأدوات التحليل. FunctionTrace هي أحد هذه المشاريع. يروي مات قصة صنع الآلة.
الوظيفة
لقد قمت مؤخرًا بإنشاء أداة لمساعدة المطورين على فهم ما يحدث في كود Python بشكل أفضل. FunctionTrace هي أداة تعريف عدم أخذ العينات لبايثون والتي تعمل على تطبيقات غير معدلة بنفقات منخفضة للغاية - أقل من 5٪. من المهم ملاحظة أنه مدمج مع Firefox Profiler. يتيح لك هذا التفاعل مع الملفات الشخصية بيانياً ، مما يسهل اكتشاف الأنماط وإجراء تغييرات على قاعدة التعليمات البرمجية الخاصة بك.
سأستعرض أهداف تطوير FunctionTrace وأشارك تفاصيل التنفيذ الفني. في النهاية سنلعب مع القليل من العرض .

تم فتح مثال على ملف تعريف FunctionTrace في Firefox Profiler.
الديون التقنية كدافع
تميل قواعد التشفير إلى أن تصبح أكبر بمرور الوقت. خاصة عند العمل في مشاريع معقدة مع الكثير من الأشخاص. بعض اللغات تتعامل بشكل أفضل مع هذه المشكلة. على سبيل المثال ، كانت قدرات Java IDE موجودة منذ عقود. أو Rust وكتابته القوية ، مما يجعل إعادة البناء سهلة للغاية. يبدو أحيانًا أنه مع نمو قواعد الرموز في اللغات الأخرى ، يصبح من الصعب الحفاظ عليها. هذا ينطبق بشكل خاص على كود Python الأقدم. على الأقل نحن جميعًا بايثون 3 الآن ، أليس كذلك؟
قد يكون إجراء تغييرات واسعة النطاق أو إعادة هيكلة كود غير مألوف أمرًا بالغ الصعوبة. من الأسهل بالنسبة لي تغيير الرمز بشكل صحيح عندما أرى جميع تفاعلات البرنامج وما يفعله. في كثير من الأحيان ، أجد نفسي أعيد كتابة أجزاء من التعليمات البرمجية التي لم أقصد مطلقًا لمسها: يظهر عدم الكفاءة عندما أراه في التصور.
أردت أن أفهم ما كان يجري في الكود دون الحاجة إلى قراءة مئات الملفات. لكن لم أجد الأدوات التي تناسب احتياجاتي. أيضًا ، فقدت الاهتمام ببناء مثل هذه الأداة بنفسي بسبب مقدار عمل واجهة المستخدم. وكانت الواجهة ضرورية. تم إحياء آمالي في فهم سريع لتنفيذ البرنامج عندما عثرت على ملف تعريف Firefox.
قدم ملف التعريف جميع العناصر التي يصعب تنفيذها - واجهة مستخدم سهلة الاستخدام مفتوحة المصدر تعرض مخططات المكدس وعلامات السجل المحددة زمنياً ومخططات إطلاق النار وتعطي الاستقرار الذي تكون طبيعته ملزمة لمتصفح ويب معروف. يمكن لأي أداة قادرة على كتابة ملف تعريف JSON منسق بشكل صحيح إعادة استخدام جميع إمكانات التحليل الرسومية المذكورة سابقًا.
FunctionTrace تصميم
لحسن الحظ ، كان لدي بالفعل أسبوع إجازة مخطط له بعد أن اكتشفت ملف تعريف Firefox. وكان لدي صديق أراد تطوير آلة معي. كما أخذ يوم إجازة في ذلك الأسبوع.
الأهداف
كان لدينا العديد من الأهداف عندما بدأنا في تطوير FunctionTrace:
- القدرة على رؤية كل ما يحدث في البرنامج.
- .
- , .
الهدف الأول كان له تأثير كبير على التصميم. أضاف الأخيران تعقيدًا هندسيًا. علمنا كلانا من التجربة السابقة باستخدام أدوات مماثلة أن الإحباط هو أننا لن نرى مكالمات وظيفية قصيرة جدًا. عندما تقوم بتسجيل سجل تتبع يبلغ 1 مللي ثانية ، ولكن لديك وظائف مهمة وأسرع ، فإنك تفقد الكثير مما يحدث داخل برنامجك.
علمنا أيضًا أننا بحاجة إلى تتبع جميع مكالمات الوظائف. لذلك ، لم نتمكن من استخدام ملف تعريف العينات. أيضًا ، لقد قضيت مؤخرًا بعض الوقت مع الكود حيث تنفذ وظائف Python كود Python الآخر ، غالبًا من خلال برنامج نصي للوسيط. بناءً على ذلك ، أردنا أن نكون قادرين على تتبع العمليات الفرعية.
التنفيذ الأولي
لدعم عمليات ومتفرعات متعددة ، استقرنا على نموذج خادم العميل. يرسل عملاء Python بيانات التتبع إلى خادم Rust. يقوم الخادم بتجميع البيانات وضغطها قبل إنشاء ملف تعريف ، والذي يمكن أن يستهلكه ملف تعريف Firefox. اخترنا Rust لعدة أسباب ، بما في ذلك الكتابة القوية والسعي لتحقيق أداء ثابت واستخدام الذاكرة الذي يمكن التنبؤ به وسهولة إنشاء النماذج الأولية وإعادة البناء.
لقد صممنا نموذجًا أوليًا للعميل كوحدة Python تسمى
python -m functiontrace code.py. هذا جعل من السهل استخدام خطافات التتبع المدمجة لتسجيل التنفيذ. بدا التنفيذ الأصلي كما يلي:
def profile_func(frame, event, arg):
if event == "call" or event == "return" or event == "c_call" or event == "c_return":
data = (event, time.time())
server.sendall(json.dumps(data))
sys.setprofile(profile_func)
الخادم يستمع على مقبس مجال يونكس . ثم يتم قراءة البيانات من العميل وتحويلها إلى JSON بواسطة ملف تعريف Firefox .
يدعم ملف التعريف أنواعًا مختلفة من الملفات الشخصية مثل سجلات الأداء . لكننا قررنا إنشاء JSON لتنسيق ملف التعريف الداخلي. يتطلب مساحة وصيانة أقل من إضافة تنسيق مدعوم جديد. من المهم ملاحظة أن ملف التعريف يحافظ على التوافق مع الإصدارات السابقة بين إصدارات ملف التعريف. هذا يعني أن أي ملف تعريف مصمم للإصدار الحالي من التنسيق يتم تحويله تلقائيًا إلى أحدث إصدار عند تنزيله في المستقبل. يشير ملف التعريف أيضًا إلى السلاسل ذات المعرفات الصحيحة. هذا يوفر مساحة كبيرة عن طريق استخدام إلغاء المضاعفة (وهو أمر تافه للاستخدامindexmap ).
عدة تحسينات
في الغالب يعمل الرمز الأصلي. في كل وظيفة استدعاء وعودة ، دعا Python الخطاف. أرسل الخطاف رسالة JSON إلى الخادم عبر مقبس لتحويله إلى التنسيق المطلوب. لكنها كانت بطيئة بشكل لا يصدق. حتى بعد تجميع مكالمات المقبس ، رأينا ما لا يقل عن ثمانية أضعاف النفقات العامة لبعض برامج الاختبار.
بعد رؤية هذه التكاليف ، نزلنا إلى المستوى C باستخدام واجهة برمجة تطبيقات C لـ Python . وحصلوا على معامل إضافي 1.1 في نفس البرامج. بعد ذلك، كنا قادرين على تنفيذ الأمثل رئيسيا آخر، لتحل محل المكالمات
time.time()إلى RDTSC عمليات عبرclock_gettime()... لقد قمنا بتقليل الحمل الزائد لأداء وظائف الاتصال إلى تعليمات متعددة وإنشاء 64 بت من البيانات. هذا أكثر فاعلية بكثير من تسلسل استدعاءات بايثون والحسابات المعقدة في مسار المهمة الحرجة.
ذكرت أن تتبع سلاسل العمليات المتعددة والعمليات الفرعية مدعوم. يعد هذا أحد أصعب أجزاء العميل ، لذلك يجدر مناقشة بعض التفاصيل ذات المستوى الأدنى.
دعم تيارات متعددة
يتم تثبيت معالج كافة الخيوط عبر
threading.setprofile(). نسجل من خلال معالج مثل هذا عندما نقوم بإعداد حالة مؤشر الترابط. هذا يضمن تشغيل Python و GIL قيد التشغيل. هذا يبسط بعض الافتراضات:
// This is installed as the setprofile() handler for new threads by
// threading.setprofile(). On its first execution, it initializes tracing for
// the thread, including creating the thread state, before replacing itself with
// the normal Fprofile_FunctionTrace handler.
static PyObject* Fprofile_ThreadFunctionTrace(..args..) {
Fprofile_CreateThreadState();
// Replace our setprofile() handler with the real one, then manually call
// it to ensure this call is recorded.
PyEval_SetProfile(Fprofile_FunctionTrace);
Fprofile_FunctionTrace(..args..);
Py_RETURN_NONE;
}
عندما يتم استدعاء الخطاف
Fprofile_ThreadFunctionTrace()، فإنه يخصص الهيكل ThreadState. يحتوي هذا الهيكل على المعلومات التي يحتاجها الخيط لتسجيل الأحداث والتواصل مع الخادم. ثم نرسل رسالة init إلى خادم الملف الشخصي. هنا نقوم بإخطار الخادم لبدء دفق جديد وتقديم بعض المعلومات الأولية: الوقت ، PID ، إلخ. بعد التهيئة ، نستبدل الخطاف بآخر Fprofile_FunctionTrace()يقوم بالتتبع الفعلي.
دعم عمليات الطفل
عند العمل مع عمليات متعددة ، نفترض أن العمليات الفرعية تبدأ من خلال مترجم Python. لسوء الحظ ، لا يتم استدعاء العمليات الفرعية
-m functiontrace، لذلك لا نعرف كيفية تعقبها. لضمان مراقبة العمليات الفرعية ، يتم تغيير متغير البيئة $ PATH عند بدء التشغيل . هذا يضمن أن Python تشير إلى ملف تنفيذي يعرف ما functiontraceيلي:
# Generate a temp directory to store our wrappers in. We'll temporarily
# add this directory to our path.
tempdir = tempfile.mkdtemp(prefix="py-functiontrace")
os.environ["PATH"] = tempdir + os.pathsep + os.environ["PATH"]
# Generate wrappers for the various Python versions we support to ensure
# they're included in our PATH.
wrap_pythons = ["python", "python3", "python3.6", "python3.7", "python3.8"]
for python in wrap_pythons:
with open(os.path.join(tempdir, python), "w") as f:
f.write(PYTHON_TEMPLATE.format(python=python))
os.chmod(f.name, 0o755)
المترجم مع وسيطة
-m functiontraceيسمى داخل الغلاف. أخيرًا ، يتم إضافة متغير بيئة عند بدء التشغيل. يشير المتغير إلى المقبس المستخدم للاتصال بخادم ملف التعريف. إذا قام العميل بالتهيئة ورأى متغير بيئة تم تعيينه بالفعل ، فإنه يتعرف على العملية التابعة. ثم يتصل بمثيل الخادم الحالي ، مما يسمح بربط تتبعه مع مثيل العميل الأصلي.
FunctionTrace الآن
يشتمل تنفيذ FunctionTrace اليوم على الكثير من القواسم المشتركة مع التنفيذ الموضح أعلاه. على مستوى عال يتم تعقب العملاء من خلال FunctionTrace مكالمة من هذا القبيل:
python -m functiontrace code.py. يقوم هذا الخط بتحميل وحدة Python لبعض التخصيصات ثم يستدعي الوحدة C لتعيين خطافات التتبع المختلفة. تتضمن هذه الخطافات خطاطيف sys.setprofileتخصيص الذاكرة المذكورة أعلاه ، بالإضافة إلى خطافات مخصصة مع ميزات مثيرة للاهتمام مثل builtins.printأو builtins.__import__. بالإضافة إلى ذلك ، functiontrace-serverيتم إنتاج مثيل ، ويتم إعداد مأخذ توصيل للتواصل معه ، ومن المؤكد أن سلاسل العمليات والعمليات الفرعية المستقبلية تتواصل مع نفس الخادم.
في كل حدث تتبع ، يرسل عميل Python إدخال MessagePack... يحتوي على الحد الأدنى من معلومات الحدث والطابع الزمني في المخزن المؤقت لذاكرة الدفق. عندما يمتلئ المخزن المؤقت (كل 128 كيلوبايت) ، يتم تدفقه إلى الخادم عبر المقبس المشترك ويستمر العميل في القيام بعمله. يستمع الخادم إلى كل عميل بشكل غير متزامن ، ويستهلك عمليات التتبع بسرعة في مخزن مؤقت منفصل لتجنب حظرهم. يمكن بعد ذلك أن يحلل مؤشر الترابط المطابق لكل عميل كل حدث تتبع وتحويله إلى التنسيق النهائي المناسب. بعد خروج جميع العملاء المتصلين ، يتم دمج سجلات كل موضوع في سجل ملف تعريف كامل. أخيرًا ، يتم إرسال كل هذا إلى ملف ، والذي يمكن استخدامه بعد ذلك مع ملف تعريف Firefox.
الدروس المستفادة
توفر وحدة Python C مزيدًا من القوة والأداء ، ولكن في نفس الوقت لها تكلفة عالية. مطلوب المزيد من التعليمات البرمجية ، يصعب العثور على وثائق جيدة ، تتوفر ميزات قليلة بسهولة. يبدو أن وحدات C أداة غير مستغلة لكتابة وحدات Python عالية الأداء. أقول هذا بناءً على بعض ملفات تعريف FunctionTrace التي رأيتها. نوصي بالتوازن. اكتب معظم التعليمات البرمجية غير المؤدية والمهمة في Python واستدع الحلقات الداخلية أو كود إعداد C لأجزاء البرنامج التي لا تتألق فيها Python.
يمكن أن يكون تشفير وفك تشفير JSON بطيئين بشكل لا يصدق عندما لا تكون هناك حاجة لسهولة القراءة. انتقلنا إلى
MessagePackللاتصال بخادم العميل ووجدت أنه من السهل التعامل معه ، مع تقليل وقت بعض المعايير إلى النصف!
يعد دعم التنميط متعدد الخيوط في بايثون أمرًا صعبًا للغاية. من المفهوم لماذا لم تكن ميزة رئيسية في ملفات تعريف بايثون السابقة. استغرق الأمر عدة طرق مختلفة والكثير من أخطاء التجزئة قبل أن نحصل على فكرة جيدة عن كيفية العمل مع GIL مع الحفاظ على الأداء العالي.
يمكنك الحصول على مهنة مطلوبة من البداية أو في المستوى الأعلى في المهارات والراتب من خلال دورات SkillFactory عبر الإنترنت:
- دورة بايثون لتطوير الويب (9 أشهر)
- مطور ويب محترف (8 أشهر)
- تدريب مهنة علم البيانات من الصفر (12 شهرًا)
- - Data Science (14 )
- - Data Analytics (5 )
- (18 )
E
- Machine Learning (12 )
- « Machine Learning Data Science» (20 )
- «Machine Learning Pro + Deep Learning» (20 )
- (6 )
- DevOps (12 )
- iOS- (12 )
- Android- (18 )
- Java- (18 )
- JavaScript (12 )
- UX- (9 )
- Web- (7 )
