يتفاجأ بعض مبرمجي Python عندما يكتشفون عدد العناصر المؤقتة التي يخصصها مترجم بيثون أثناء تشغيل نص برمجي بسيط.
يسمح لك CPython بالحصول على إحصائيات حول الكائنات المخصصة ، لذلك تحتاج إلى تجميعها مع علامات إضافية.
./configure CFLAGS='-DCOUNT_ALLOCS' --with-pydebug make -s -j2
بعد التجميع ، يمكننا فتح الرد التفاعلي والتحقق من الإحصائيات:
>>> import sys >>> sys.getcounts() [('iterator', 7, 7, 4), ('functools._lru_cache_wrapper', 1, 0, 1), ('re.Match', 2, 2, 1), ('re.Pattern', 3, 2, 1), ('SubPattern', 10, 10, 8), ('Pattern', 3, 3, 1), ('IndexError', 4, 4, 1), ('Tokenizer', 3, 3, 1), ('odict_keys', 1, 1, 1), ('odict_iterator', 18, 18, 1), ('odict_items', 17, 17, 1), ('RegexFlag', 18, 8, 10), ('operator.itemgetter', 4, 0, 4), ('PyCapsule', 1, 1, 1), ('Repr', 1, 0, 1), ('_NamedIntConstant', 74, 0, 74), ('collections.OrderedDict', 5, 0, 5), ('EnumMeta', 5, 0, 5), ('DynamicClassAttribute', 2, 0, 2), ('_EnumDict', 5, 5, 1), ('TypeError', 1, 1, 1), ('method-wrapper', 365, 365, 2), ('_C', 1, 1, 1), ('symtable entry', 5, 5, 2), ('OSError', 1, 1, 1), ('Completer', 1, 0, 1), ('ExtensionFileLoader', 2, 0, 2), ('ModuleNotFoundError', 2, 2, 1), ('_Helper', 1, 0, 1), ('_Printer', 3, 0, 3), ('Quitter', 2, 0, 2), ('enumerate', 5, 5, 1), ('_io.IncrementalNewlineDecoder', 1, 1, 1), ('map', 25, 25, 1), ('_Environ', 2, 0, 2), ('async_generator', 2, 1, 1), ('coroutine', 2, 2, 1), ('zip', 1, 1, 1), ('longrange_iterator', 1, 1, 1), ('range_iterator', 7, 7, 1), ('range', 14, 14, 2), ('list_reverseiterator', 2, 2, 1), ('dict_valueiterator', 1, 1, 1), ('dict_values', 2, 2, 1), ('dict_keyiterator', 25, 25, 1), ('dict_keys', 5, 5, 1), ('bytearray_iterator', 1, 1, 1), ('bytearray', 4, 4, 1), ('bytes_iterator', 2, 2, 1), ('IncrementalEncoder', 2, 0, 2), ('_io.BufferedWriter', 2, 0, 2), ('IncrementalDecoder', 2, 1, 2), ('_io.TextIOWrapper', 4, 1, 4), ('_io.BufferedReader', 2, 1, 2), ('_abc_data', 39, 0, 39), ('mappingproxy', 199, 199, 1), ('ABCMeta', 39, 0, 39), ('CodecInfo', 1, 0, 1), ('str_iterator', 7, 7, 1), ('memoryview', 60, 60, 2), ('managedbuffer', 31, 31, 1), ('slice', 589, 589, 1), ('_io.FileIO', 33, 30, 5), ('SourceFileLoader', 29, 0, 29), ('set', 166, 101, 80), ('StopIteration', 33, 33, 1), ('FileFinder', 11, 0, 11), ('os.stat_result', 145, 145, 1), ('ImportError', 2, 2, 1), ('FileNotFoundError', 10, 10, 1), ('ZipImportError', 12, 12, 1), ('zipimport.zipimporter', 12, 12, 1), ('NameError', 4, 4, 1), ('set_iterator', 46, 46, 1), ('frozenset', 50, 0, 50), ('_ImportLockContext', 113, 113, 1), ('list_iterator', 305, 305, 5), ('_thread.lock', 92, 92, 10), ('_ModuleLock', 46, 46, 5), ('KeyError', 67, 67, 2), ('_ModuleLockManager', 46, 46, 5), ('generator', 125, 125, 1), ('_installed_safely', 52, 52, 5), ('method', 1095, 1093, 14), ('ModuleSpec', 58, 4, 54), ('AttributeError', 22, 22, 1), ('traceback', 154, 154, 3), ('dict_itemiterator', 45, 45, 1), ('dict_items', 46, 46, 1), ('object', 8, 1, 7), ('tuple_iterator', 631, 631, 3), ('cell', 71, 31, 42), ('classmethod', 58, 0, 58), ('property', 18, 2, 16), ('super', 360, 360, 1), ('type', 78, 3, 75), ('function', 1705, 785, 922), ('frame', 5442, 5440, 36), ('code', 1280, 276, 1063), ('bytes', 2999, 965, 2154), ('Token.MISSING', 1, 0, 1), ('stderrprinter', 1, 1, 1), ('MemoryError', 16, 16, 16), ('sys.thread_info', 1, 0, 1), ('sys.flags', 2, 0, 2), ('types.SimpleNamespace', 1, 0, 1), ('sys.version_info', 1, 0, 1), ('sys.hash_info', 1, 0, 1), ('sys.int_info', 1, 0, 1), ('float', 584, 569, 20), ('sys.float_info', 1, 0, 1), ('module', 56, 0, 56), ('staticmethod', 16, 0, 16), ('weakref', 505, 82, 426), ('int', 3540, 2775, 766), ('member_descriptor', 246, 10, 239), ('list', 992, 919, 85), ('getset_descriptor', 240, 4, 240), ('classmethod_descriptor', 12, 0, 12), ('method_descriptor', 678, 0, 678), ('builtin_function_or_method', 1796, 1151, 651), ('wrapper_descriptor', 1031, 5, 1026), ('str', 16156, 9272, 6950), ('dict', 1696, 900, 810), ('tuple', 10367, 6110, 4337)]
دعونا نجعل النتيجة أكثر قراءة:
def print_allocations(top_k=None): allocs = sys.getcounts() if top_k: allocs = sorted(allocs, key=lambda tup: tup[1], reverse=True)[0:top_k] for obj in allocs: alive = obj[1]-obj[2] print("Type {}, allocs: {}, deallocs: {}, max: {}, alive: {}".format(*obj,alive))
>>> print_allocations(10) Type str, allocs: 17328, deallocs: 10312, max: 7016, alive: 7016 Type tuple, allocs: 10550, deallocs: 6161, max: 4389, alive: 4389 Type frame, allocs: 5445, deallocs: 5442, max: 36, alive: 3 Type int, allocs: 3988, deallocs: 3175, max: 813, alive: 813 Type bytes, allocs: 3031, deallocs: 1044, max: 2154, alive: 1987 Type builtin_function_or_method, allocs: 1809, deallocs: 1164, max: 651, alive: 645 Type dict, allocs: 1726, deallocs: 930, max: 815, alive: 796 Type function, allocs: 1706, deallocs: 811, max: 922, alive: 895 Type code, allocs: 1284, deallocs: 304, max: 1063, alive: 980 Type method, allocs: 1095, deallocs: 1093, max: 14, alive: 2
أين:
- allocs - عدد الأشياء التي تم تخصيصها منذ بداية المترجم
- deallocs - عدد الكائنات التي تم حذفها (يدويًا أو تلقائيًا)
- على قيد الحياة - عدد الكائنات الحية (الحالية) (allocs - deallocs)
- max - أقصى عدد من الكائنات الحية منذ بداية المترجم
كما ترون ، تمكنت Python REPL الفارغة من تخصيص
17328 صفًا و
10550 صفًا. هذه كمية مجنونة من الأشياء! هنا يجب أن تضع في اعتبارك أنه لكي تعمل REPL ، تقوم Python تلقائيًا باستيراد وحدات إضافية لا يتم استيرادها في حالة البرامج النصية الفارغة.
الآن دعونا نختبر "Hello، World" على قارورة:
import sys from flask import Flask app = Flask(__name__) @app.route('/') def hello_world(): print_allocations(15) return 'Hello, World!'
./python -m flask run ab -n 100 http://127.0.0.1:5000/
بعد إرسال 100 طلب HTTP إلى خادمنا ، تبدو الإحصائيات كما يلي:
Type str, allocs: 192649, deallocs: 138892, max: 54320, alive: 53757 Type frame, allocs: 191752, deallocs: 191714, max: 158, alive: 38 Type tuple, allocs: 183474, deallocs: 150069, max: 33581, alive: 33405 Type int, allocs: 85154, deallocs: 81100, max: 4115, alive: 4054 Type bytes, allocs: 31671, deallocs: 14331, max: 17381, alive: 17340 Type list, allocs: 29846, deallocs: 27541, max: 2415, alive: 2305 Type builtin_function_or_method, allocs: 28525, deallocs: 27572, max: 957, alive: 953 Type dict, allocs: 19900, deallocs: 14800, max: 5280, alive: 5100 Type method, allocs: 15170, deallocs: 15105, max: 74, alive: 65 Type function, allocs: 14761, deallocs: 7086, max: 7711, alive: 7675 Type slice, allocs: 12521, deallocs: 12521, max: 1, alive: 0 Type list_iterator, allocs: 10795, deallocs: 10795, max: 35, alive: 0 Type code, allocs: 9849, deallocs: 1749, max: 8107, alive: 8100 Type tuple_iterator, allocs: 8938, deallocs: 8938, max: 4, alive: 0 Type float, allocs: 6033, deallocs: 5889, max: 152, alive: 144
كما ترون ، خصصت القارورة
847،261 قطعة منذ بداية المترجم. معظمهم كانوا مؤقتين (
714،336 ) وتمت إزالتهم بمجرد عدم الحاجة إليهم. الكائنات المتبقية (
132 925 ) لا تزال في الذاكرة.
الإطارات وكائنات التعليمات البرمجية
في المثال أعلاه ، يمكنك العثور على العديد من كائنات الإطارات والتعليمات البرمجية. لماذا هم بحاجة؟
باختصار ، كل كائن كود يخزن في حد ذاته كتلة من التعليمات البرمجية المترجمة ، في المقابل ، يتم استخدام كائنات الإطار لتنفيذها ، والعمل على مبدأ
مكدس الاستدعاء . في Python ، تعد الكتلة الأكثر شيوعًا دالة. تحتاج كل وظيفة جديدة إلى كائن التعليمات البرمجية الخاص بها ، وكل استدعاء لهذه الوظيفة يحتاج إلى كائن إطار منفصل ، حيث ستقوم Python بتخزين المتغيرات المحلية. بالإضافة إلى المتغيرات المحلية ، يقوم كل كائن إطار بتخزين الكثير من البيانات المساعدة المطلوبة لأداء الوظيفة.
من أين تأتي كل هذه الأشياء؟
بايثون لغة ديناميكية للغاية وعليك أن تدفع مقابلها. من أجل دعم القدرات الديناميكية ، قام بإنشاء عدد كبير من الكائنات المؤقتة التي تلعب دورًا داعمًا.
على سبيل المثال ، يؤدي الإعلان عن وظيفة بسيطة إلى إنشاء ما لا يقل عن 5 قواميس و 5 صفوف و 4 قوائم. ستعيش هذه الكائنات حتى نهاية البرنامج النصي. في المقابل ، تخزن كل هذه الكائنات كائنات أخرى (عناصرها) في حد ذاتها ، وهي العشرات ، وأحيانًا مئات الكائنات الإضافية المستخدمة في الوصف الداخلي للدالة المترجمة. يمكن أن يصف وصف الفئة المتوسطة مئات الكائنات (القواميس ، الصفوف ، القوائم) الحاوية. لسوء الحظ ، لن يكون من الممكن هنا حساب العدد الدقيق للكائنات المخصصة تلقائيًا ، وهذه الأرقام تقريبية.
لكي تقوم Python بتخصيص عدد كبير من الكائنات بسرعة ، فإنها تستخدم نظامًا
كبيرًا متعدد الطبقات يعمل على تحسين تخصيص الكائنات في الذاكرة.
يتساءل المرء أحيانًا عن مقدار التفاصيل التي تخفيها اللغات المفسرة عنا. يسمح لك Python بكتابة كود جيد دون التفكير في الكثير من المشاكل والتفاصيل.
ملاحظة: أنا مؤلف هذا المقال ، يمكنك طرح أي أسئلة.