Beberapa programmer Python sangat terkejut ketika mereka mengetahui berapa banyak objek sementara yang dialokasikan oleh juru bahasa python sementara sebuah skrip sederhana sedang berjalan.
CPython memungkinkan Anda untuk mendapatkan statistik pada objek yang dialokasikan, untuk ini Anda perlu mengompilasinya dengan flag tambahan.
./configure CFLAGS='-DCOUNT_ALLOCS' --with-pydebug make -s -j2
Setelah kompilasi, kami dapat membuka REPL interaktif dan memeriksa statistik:
>>> 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)]
Mari kita simpulkan lebih mudah dibaca:
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
Dimana:
- allocs - berapa banyak objek yang telah dialokasikan sejak permulaan penerjemah
- deallocs - berapa banyak objek yang dihapus (secara manual atau otomatis)
- hidup - jumlah objek hidup (saat ini) (allocs - deallocs)
- maks - jumlah maksimum objek hidup sejak dimulainya penerjemah
Seperti yang Anda lihat, REPL Python kosong berhasil mengalokasikan
17.328 baris dan
10.550 tupel. Ini adalah sejumlah benda gila! Di sini Anda perlu diingat bahwa agar REPL berfungsi, Python secara otomatis mengimpor modul tambahan yang tidak diimpor dalam kasus skrip kosong.
Sekarang mari kita uji "Halo, Dunia" pada labu:
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/
Setelah mengirim 100 permintaan HTTP ke server kami, statistiknya terlihat seperti ini:
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
Seperti yang Anda lihat, labu telah mengalokasikan
847.261 objek sejak dimulainya penerjemah. Sebagian besar dari mereka bersifat sementara (
714.336 ) dan dihapus segera setelah mereka tidak lagi diperlukan. Objek yang tersisa (
132 925 ) masih dalam memori.
Bingkai dan objek kode
Dalam contoh di atas, Anda dapat menemukan banyak objek bingkai dan kode. Mengapa mereka dibutuhkan?
Singkatnya, setiap objek kode menyimpan sendiri blok kode yang dikompilasi, pada gilirannya, objek frame digunakan untuk mengeksekusinya, bekerja berdasarkan prinsip
tumpukan panggilan . Dalam Python, blok paling populer adalah fungsi. Setiap fungsi baru memerlukan objek kode sendiri, dan setiap panggilan ke fungsi ini membutuhkan objek bingkai terpisah, di mana Python akan menyimpan variabel lokal. Selain variabel lokal, setiap objek bingkai menyimpan banyak data tambahan yang diperlukan untuk melakukan fungsi.
Dari mana semua benda ini berasal?
Python adalah bahasa yang sangat dinamis dan Anda harus membayarnya. Untuk mendukung kemampuan dinamis, ia menciptakan sejumlah besar objek sementara yang memainkan peran pendukung.
Misalnya, mendeklarasikan fungsi sederhana membuat setidaknya 5 kamus, 5 tupel, dan 4 daftar. Objek-objek ini akan hidup sampai akhir skrip. Pada gilirannya, semua objek ini menyimpan objek lain (elemen mereka) dalam diri mereka sendiri, ini adalah puluhan, terkadang ratusan objek tambahan yang digunakan untuk deskripsi internal dari fungsi yang dikompilasi. Deskripsi kelas rata-rata dapat menyorot ratusan objek (kamus, tupel, daftar) objek. Sayangnya, di sini tidak akan mungkin untuk secara otomatis menghitung jumlah tepat objek yang dialokasikan, dan angka-angka ini merupakan perkiraan.
Agar Python cepat mengalokasikan sejumlah besar objek, ia menggunakan sistem
besar dan multi-layer yang mengoptimalkan alokasi objek dalam memori.
Kadang-kadang orang bertanya-tanya berapa banyak detail bahasa yang ditafsirkan disembunyikan dari kita. Python memungkinkan Anda untuk menulis kode yang baik tanpa memikirkan banyak masalah dan detail.
PS: Saya penulis artikel ini, kamu bisa bertanya.