¿Cuántos objetos emite Python al ejecutar scripts?

Algunos programadores de Python se sorprenden mucho cuando descubren cuántos objetos temporales asigna el intérprete de Python mientras se ejecuta un script simple.

CPython le permite obtener estadísticas sobre los objetos asignados, para esto necesita compilarlo con banderas adicionales.

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

Después de la compilación, podemos abrir el REPL interactivo y verificar las estadísticas:

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

Hagamos que la conclusión sea más legible:

 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 

Donde:

  • allocs: cuántos objetos se han asignado desde el inicio del intérprete
  • deallocs: cuántos objetos se eliminaron (manual o automáticamente)
  • vivo: el número de objetos vivos (actuales) (alocs - deallocs)
  • max: el número máximo de objetos vivos desde el inicio del intérprete

Como puede ver, el REPL de Python vacío logró asignar 17.328 filas y 10.550 tuplas. Esta es una cantidad loca de objetos! Aquí debe tener en cuenta que para que REPL funcione, Python importa automáticamente módulos adicionales que no se importan en el caso de scripts vacíos.

Ahora probemos "Hello, World" en el matraz:

 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/ 

Después de enviar 100 solicitudes HTTP a nuestro servidor, las estadísticas se ven así:

 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 

Como puede ver, el matraz ha asignado 847,261 objetos desde el inicio del intérprete. La mayoría de ellos eran temporales ( 714,336 ) y se eliminaron tan pronto como ya no eran necesarios. Los objetos restantes ( 132 925 ) todavía están en la memoria.

Marcos y objetos de código


En el ejemplo anterior, puede encontrar muchos objetos de marco y código. ¿Por qué son necesarios?

En resumen, cada objeto de código almacena en sí mismo un bloque de código compilado, a su vez, los objetos de marco se utilizan para ejecutarlos, trabajando según el principio de una pila de llamadas . En Python, el bloque más popular es una función. Cada nueva función necesita su propio objeto de código, y cada llamada a esta función necesita un objeto de marco separado, donde Python almacenará variables locales. Además de las variables locales, cada objeto de marco almacena una gran cantidad de datos auxiliares necesarios para realizar la función.

¿De dónde vienen todos estos objetos?


Python es un lenguaje muy dinámico y hay que pagarlo. Con el fin de soportar capacidades dinámicas, crea una gran cantidad de objetos temporales que juegan un papel de soporte.

Por ejemplo, declarar una función simple crea al menos 5 diccionarios, 5 tuplas y 4 listas. Estos objetos vivirán hasta el final del guión. A su vez, todos estos objetos almacenan otros objetos (sus elementos) en sí mismos, son docenas, a veces cientos de objetos adicionales utilizados para la descripción interna de la función compilada. La descripción de la clase promedio puede resaltar cientos de objetos contenedor (diccionarios, tuplas, listas). Desafortunadamente, aquí no será posible calcular automáticamente el número exacto de objetos asignados, y estas cifras son aproximadas.

Para que Python asigne rápidamente una gran cantidad de objetos, utiliza un sistema grande y de múltiples capas que optimiza la asignación de objetos en la memoria.

A veces uno se pregunta cuántos detalles nos ocultan los idiomas interpretados. Python le permite escribir un buen código sin pensar en muchos problemas y detalles.

PD: Soy el autor de este artículo, puedes hacer cualquier pregunta.

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


All Articles