بيثون ديس الوحدة والتواء الثوابت

مرحبا بالجميع. نريد اليوم أن نشارك ترجمة أخرى تم إعدادها تحسبا لإطلاق الدورة التدريبية "مطور الويب في بيثون" . دعنا نذهب!



لقد فوجئت مؤخرًا عندما اكتشفت ذلك


>>> pow(3,89) 

يعمل أبطأ من


 >>> 3**89 

حاولت الخروج ببعض التفسير المقبول ، لكن لم أستطع. لقد قمت بتتبع وقت تنفيذ هذين التعبرين باستخدام وحدة timeit من Python 3:


 $ python3 -m timeit 'pow(3,89)' 500000 loops, best of 5: 688 nsec per loop $ python3 -m timeit '3**89' 500000 loops, best of 5: 519 nsec per loop 

الفرق صغير. 0.1 μs فقط ، لكنه مسكون لي. إذا لم أستطع شرح أي شيء في البرمجة ، فأنا أعاني من الأرق


لقد وجدت الإجابة باستخدام تغذية Python IRC على Freenode. السبب الأسير يعمل ببطء أبطأ لأن CPython لديه بالفعل خطوة إضافية من تحميل الأسرى من مساحة الاسم. بينما عند استدعاء 3 ** 9 ، لا يلزم مثل هذا الحمل من حيث المبدأ. هذا يعني أيضًا أن هذا الفارق الزمني سيبقى ثابتًا إلى حد ما إذا زادت قيم الإدخال.


تم تأكيد الفرضية:


 $ python3 -m timeit 'pow(3,9999)' 5000 loops, best of 5: 58.5 usec per loop $ python3 -m timeit '3**9999' 5000 loops, best of 5: 57.3 usec per loop 

في عملية إيجاد حل لهذه المشكلة ، تعلمت أيضًا عن وحدة dis. انها تسمح لك لفك شفرة Python bytecode وتعلمها. لقد كان اكتشافًا مثيرًا للغاية ، حيث أنني كنت أدرس مؤخرًا الهندسة العكسية للملفات الثنائية ، وكانت الوحدة المكتشفة مفيدة في هذا الشأن.


لقد قمت بفك تشفير الرمز الفرعي للتعبيرات أعلاه وحصلت على ما يلي:


 >>> import dis >>> dis.dis('pow(3,89)') # 1 0 LOAD_NAME 0 (pow) # 2 LOAD_CONST 0 (3) # 4 LOAD_CONST 1 (89) # 6 CALL_FUNCTION 2 # 8 RETURN_VALUE >>> dis.dis('3**64') # 1 0 LOAD_CONST 0 (3433683820292512484657849089281) # 2 RETURN_VALUE >>> dis.dis('3**65') # 1 0 LOAD_CONST 0 (3) # 2 LOAD_CONST 1 (65) # 4 BINARY_POWER # 6 RETURN_VALUE 

يمكنك أن تقرأ عن كيفية فهم إخراج dis.dis بشكل صحيح من خلال الرجوع إلى هذه الإجابة على Stackoverflow.


حسنا ، العودة إلى الرمز. أسرى الحرب ترجيح منطقي. يقوم bytecode بتحميل الأسرى من مساحة الاسم ، ويتم تحميله في السجلات 3 و 89 ، وأخيراً يستدعي وظيفة الأسرى. ولكن لماذا يختلف الإخراج من اثنين من decompilations التالية؟ بعد كل شيء ، كل ما قمنا بتغييره هو قيمة الأس من 64 إلى 65!


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


 def one_plue_one(): return 1+1 # --vs-- def one_plue_one(): return 2 

بيثون يجمع الوظيفة الأولى في الثانية ويستخدمها عند تشغيل الكود. ليس سيئا ، هاه؟


فلماذا يعمل الالتواء من الثوابت لمدة 3 ** 64 ، ولكن ليس من أجل 3 ** 65؟ حسنًا ، لا أعرف. ربما يرتبط هذا بطريقة أو بأخرى بالحد من عدد الدرجات المحسوبة مسبقًا بواسطة النظام في الذاكرة. قد أكون مخطئا. الخطوة التالية التي أخطط لاتخاذها هي البحث في شفرة مصدر Python في وقت فراغي ومحاولة فهم ما يجري. ما زلت أبحث عن إجابة لسؤالي ، لذلك إذا كان لديك أي أفكار ، شاركها في التعليقات.


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


هل لاحظت أشياء مماثلة؟ في انتظار تعليقاتكم!

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


All Articles