Wie viele Objekte gibt Python beim Ausführen von Skripten aus?

Einige Python-Programmierer sind sehr überrascht, wenn sie herausfinden, wie viele temporäre Objekte der Python-Interpreter zuweist, während ein einfaches Skript ausgeführt wird.

Mit CPython können Sie Statistiken zu zugewiesenen Objekten abrufen. Dazu müssen Sie diese mit zusätzlichen Flags kompilieren.

./configure CFLAGS='-DCOUNT_ALLOCS' --with-pydebug make -s -j2 

Nach der Kompilierung können wir die interaktive REPL öffnen und die Statistiken überprüfen:

 >>> 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)] 

Lassen Sie uns die Schlussfolgerung lesbarer machen:

 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 

Wo:

  • Zuweisungen - Wie viele Objekte wurden seit dem Start des Interpreters zugewiesen?
  • Freigabe - wie viele Objekte wurden gelöscht (manuell oder automatisch)
  • lebendig - die Anzahl der lebenden (aktuellen) Objekte (Zuordnungen - Freigaben)
  • max - die maximale Anzahl lebender Objekte seit dem Start des Dolmetschers

Wie Sie sehen können, konnte die leere Python-REPL 17.328 Zeilen und 10.550 Tupel zuweisen. Das ist eine verrückte Menge an Objekten! Hier müssen Sie berücksichtigen, dass Python automatisch zusätzliche Module importiert, die bei leeren Skripten nicht importiert werden, damit REPL funktioniert.

Testen wir nun "Hello, World" auf einer Flasche:

 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/ 

Nach dem Senden von 100 HTTP-Anforderungen an unseren Server sehen die Statistiken folgendermaßen aus:

 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 

Wie Sie sehen können, hat flask seit dem Start des Interpreters 847.261 Objekte zugewiesen. Die meisten von ihnen waren vorübergehend ( 714.336 ) und wurden entfernt, sobald sie nicht mehr benötigt wurden. Die restlichen Objekte ( 132 925 ) befinden sich noch im Speicher.

Frames und Codeobjekte


Im obigen Beispiel finden Sie viele Frame- und Codeobjekte. Warum werden sie gebraucht?

Kurz gesagt, jedes Codeobjekt speichert in sich einen Block kompilierten Codes, der wiederum von Rahmenobjekten ausgeführt wird, die nach dem Prinzip eines Aufrufstapels arbeiten . In Python ist der beliebteste Block eine Funktion. Jede neue Funktion benötigt ein eigenes Codeobjekt, und jeder Aufruf dieser Funktion benötigt ein separates Frame-Objekt, in dem Python lokale Variablen speichert. Zusätzlich zu den lokalen Variablen speichert jedes Rahmenobjekt viele Hilfsdaten, die zur Ausführung der Funktion benötigt werden.

Woher kommen all diese Objekte?


Python ist eine sehr dynamische Sprache und Sie müssen dafür bezahlen. Um dynamische Funktionen zu unterstützen, erstellt er eine große Anzahl temporärer Objekte, die eine unterstützende Rolle spielen.

Wenn Sie beispielsweise eine einfache Funktion deklarieren, werden mindestens 5 Wörterbücher, 5 Tupel und 4 Listen erstellt. Diese Objekte bleiben bis zum Ende des Skripts erhalten. Alle diese Objekte speichern wiederum andere Objekte (ihre Elemente) in sich selbst. Dies sind Dutzende, manchmal Hunderte von zusätzlichen Objekten, die für die interne Beschreibung der kompilierten Funktion verwendet werden. Die Beschreibung der Durchschnittsklasse kann Hunderte von Containerobjekten (Wörterbücher, Tupel, Listen) hervorheben. Leider ist es hier nicht möglich, die genaue Anzahl der zugewiesenen Objekte automatisch zu berechnen, und diese Zahlen sind ungefähr.

Damit Python schnell eine große Anzahl von Objekten zuordnen kann, wird ein großes und mehrschichtiges System verwendet , das die Zuordnung von Objekten im Speicher optimiert.

Manchmal fragt man sich, wie viele Details die interpretierten Sprachen vor uns verbergen. Mit Python können Sie guten Code schreiben, ohne über viele Probleme und Details nachdenken zu müssen.

PS: Ich bin der Autor dieses Artikels, Sie können alle Fragen stellen.

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


All Articles