IlmHamroh
Python kursi/Obyekt modeli va xotira8/10-dars25 daqiqa
Mundarija (20)

11.8-dars: sys.getsizeof va haqiqiy hajm

11-QISM — OBYEKT MODELI VA XOTIRA · 8-dars


1. Kirish va motivatsiya

Dasturchi foydalanuvchi sessiyalarini xotiradagi keshda saqlashga qaror qildi va "qancha joy oladi" deb tekshirdi:

python
>>> sys.getsizeof(kesh)
8_388_696          # ~8 MB — hech gap emas

Servis ishga tushdi, bir soatdan keyin konteyner 2 GB chegarasiga urilib o'ldi (OOM). Sabab: sys.getsizeof lug'atning o'zini — kalit va qiymatlarga ko'rsatkichlar jadvalini — o'lchadi. Jadvaldagi har bir sessiya lug'ati, uning ichidagi satrlar, ro'yxatlar va obyektlar hisobga kirmadi. Haqiqiy hajm 1.7 GB edi.

sys.getsizeof — sayoz o'lchov: obyektning o'ziga ajratilgan xotira, u havola qilgan obyektlarsiz. Haqiqiy hajmni bilish uchun obyektlar grafini aylanib chiqish yoki ajratilgan xotirani to'g'ridan-to'g'ri kuzatish (tracemalloc) kerak.

Bu darsda:

  • getsizeof nimani o'lchaydi, nimani o'lchamaydi
  • Asosiy turlarning hajmi: int, float, str (to'rt xil ko'rinish), bytes
  • Konteynerlar: ko'rsatkichlar jadvali va zaxira joy (over-allocation)
  • Chuqur hajm: rekursiv hisoblash va uning tuzoqlari
  • tracemalloc bilan haqiqiy ajratilgan xotirani o'lchash
  • Python 3.13+ obyektlari: atributlar getsizeof ga kirmaydi
  • Amaliy: xotira byudjeti bilan ishlaydigan kesh

2. Nazariya — chuqur tushuntirish

2.1. getsizeof nima qaytaradi

python
sys.getsizeof(obj)            # baytlarda
sys.getsizeof(obj, standart)  # hajmni aniqlab bo'lmasa — standart qiymat

Ichkarida:

getsizeof(obj) = type(obj).__sizeof__(obj) + GC sarlavhasi (agar obyekt GC kuzatadigan turda bo'lsa)
Kiradi Kirmaydi
Obyekt sarlavhasi: havolalar sanog'i, tur ko'rsatkichi Havola qilingan obyektlar (ro'yxat elementlari, lug'at kalitlari va qiymatlari)
O'z ichidagi ma'lumot: satr belgilari, son raqamlari 3.13+ oddiy obyektlarning atribut qiymatlari
Konteynerning ko'rsatkichlar jadvali va zaxira joyi Tur (klass) obyekti
GC sarlavhasi (konteynerlar uchun, 64 bitda 16 bayt) Xotira ajratuvchining yumaloqlashi va fragmentatsiyasi

O'z klassingizda __sizeof__ ni qayta aniqlash mumkin — getsizeof unga GC sarlavhasini qo'shadi. Manfiy qiymat — ValueError.

2.2. Asosiy turlar (CPython 3.14, 64 bit)

Tur Hajm Izoh
int 28 bayt, har 30 bitlik "raqam"ga +4 0, True, 2**30 - 1 → 28; 2**30 → 32; 10**100 → 72
float 24 Har doim
complex 32
bytes 33 + n
str ASCII 41 + n Har belgi 1 bayt
str latin-1 (é, ñ) 57 + n Har belgi 1 bayt, sarlavha kattaroq
str UCS-2 (ş, ğ, €) 58 + 2n Har belgi 2 bayt
str UCS-4 (emoji) 60 + 4n Har belgi 4 bayt
range 48 Uzunlikdan qat'i nazar
Generator ~200 Qancha element berishidan qat'i nazar

Satr hajmi uning eng "katta" belgisiga bog'liq (PEP 393): 1000 ta ASCII harfli satrga bitta emoji qo'shilsa, u taxminan to'rt barobar kattalashadi.

2.3. Konteynerlar

Konteyner faqat ko'rsatkichlarni saqlaydi — har biri 8 bayt:

list  = 56 + 8 × sig'im        (sig'im ≥ uzunlik)
tuple = 48 + 8 × uzunlik       (() → 48, (1,) → 56, (1, 2) → 64) — zaxirasiz
dict  = 64 (bo'sh), 1 elementdan 184, keyin sakrab o'sadi
set   = 216 (bo'sh), keyin sakrab o'sadi

Zaxira joy (over-allocation). append har safar qayta ajratmasligi uchun ro'yxat oldindan kattaroq joy oladi (6.4-dars):

Uzunlik 0 1–4 5–8 9–16 17–24 25–32
getsizeof 56 88 120 184 248 312

Shuning uchun bir xil elementli ikki ro'yxat turli hajmda bo'lishi mumkin: [0] * 10 (136) va append bilan to'ldirilgan ro'yxat (184).

getsizeof([x] * 1000) bilan getsizeof([1000 xil satr]) deyarli bir xil — elementlar o'lchamiga qaramaydi.

2.4. Chuqur hajm

"Obyekt va unga bog'liq hamma narsa" hajmi — obyektlar grafini aylanib chiqish:

python
def chuqur_hajm(obj, korilgan):
    if id(obj) in korilgan:
        return 0
    korilgan.add(id(obj))
    hajm = sys.getsizeof(obj)
    ... ichidagi obyektlar uchun rekursiya

Tuzoqlar:

Tuzoq Yechim
Bir obyektga ko'p havola ([x] * 1000) id bo'yicha ko'rilganlar to'plami
Sikllar (11.5-dars) Xuddi shu — ko'rilganlar to'plami
Umumiy obyektlar: kichik sonlar, None, interned satrlar O'lmaslarni (sys._is_immortal) va kerak bo'lsa interned satrlarni (sys._is_interned) hisobga olmaslik — 3.14 da interned satrlar o'lmas emas (Misol 4)
Klasslar, modullar, funksiyalar Ularga kirmaslik — aks holda butun dastur hisoblanadi
vars(obj) chaqiruvi 3.13+ da __dict__ ni yaratib yuboradi O'lchash obyektni o'zgartirishi mumkinligini bilish
C kengaytma obyektlari (NumPy) O'zining __sizeof__ / nbytes

"Kimga tegishli" savoli ham noaniq: ikki ro'yxat bitta satrni ulashsa, u qaysi biriga hisoblanadi? Chuqur hajm — taxmin, aniq javob emas.

2.5. tracemalloc

tracemalloc Python xotira ajratuvchisining har bir ajratishini kuzatadi:

python
tracemalloc.start()
malumot = yukla()
joriy, eng_yuqori = tracemalloc.get_traced_memory()
tracemalloc.stop()
getsizeof chuqur hajm tracemalloc
Nimani o'lchaydi Bitta obyekt Obyektlar grafi Kod bajarilgan vaqtda ajratilgan hamma narsa
Ulashilgan obyektlar — Bir marta Faqat yangi yaratilganlar
Yashirin joy (3.13+ atributlar, keshlar) Qisman
Narxi Juda arzon Graf hajmiga bog'liq Kodni 1.5–3 barobar sekinlashtiradi
Qayerda Kodda, tez taxmin Byudjet, monitoring Tahlil, testlar

tracemalloc ham operatsion tizim ko'radigan jarayon hajmi (RSS) emas: bo'shatilgan xotira OS ga darhol qaytmaydi, C kutubxonalarining o'z ajratishlari kirmaydi (29-qism).

2.6. Python 3.13+ obyektlari

Oddiy klass nusxasining atributlari 3.13 dan boshlab alohida __dict__ lug'atida emas, obyektning o'zida ("inline values") saqlanadi — __dict__ faqat so'ralganda yaratiladi. getsizeof bu qismni ko'rsatmaydi:

100 000 nusxa, 2 atribut getsizeof tracemalloc bo'yicha bir nusxa
Oddiy klass 48 ~88
@dataclass 48 ~88
__slots__ 48 ~48
namedtuple 64 ~72
dict 184 ~184

Aniq raqamlar versiyaga bog'liq — xulosa: obyektlar hajmini getsizeof bilan solishtirmang, tracemalloc bilan o'lchang. __slots__ tafsilotlari — 11.9-darsda.


3. Tez ma'lumotnoma

O'lchov vositalari

sys.getsizeof(obj)        → sayoz: obyektning o'zi (+ GC sarlavhasi)
obj.__sizeof__()          → GC sarlavhasisiz
chuqur_hajm(obj)          → graf bo'yicha taxmin (ko'rilganlar, o'lmaslarsiz)
tracemalloc               → haqiqiy ajratilgan xotira

Hajmlar (3.14, 64 bit)

int 28 (+4 har 30 bit)   float 24   bytes 33+n
str: ASCII 41+n, latin-1 57+n, UCS-2 58+2n, UCS-4 60+4n
list 56 + 8×sig'im   tuple 48 + 8×n   dict 64 / 184+   set 216+
range 48   generator ~200

Qoidalar

konteyner getsizeof → faqat ko'rsatkichlar
bitta emoji → butun satr 4 bayt/belgi
obyekt nusxalari → tracemalloc bilan

4. Batafsil misollar

Misol 1 — Turlar bo'yicha getsizeof

python
"""int raqamlari, satrning to'rt ko'rinishi, konteynerlar va zaxira joy, range/generator, GC sarlavhasi."""

import sys

g = sys.getsizeof

print("=== 1. int: har 30 bitga 4 bayt ===")
for n in [0, 1, 2**30 - 1, 2**30, 2**60 - 1, 2**60, 10**100]:
    print(f"  {n.bit_length():>3} bit  → {g(n)} bayt")

print("\n=== 2. str: eng katta belgi hamma belgi hajmini belgilaydi ===")
namunalar = {
    "ASCII": "Toshkent",
    "latin-1": "Montréal",
    "UCS-2": "Qarşı ş",
    "UCS-4": "Salom 🙂",
}
for tur, s in namunalar.items():
    print(f"  {tur:8} {s!r:12} uzunlik {len(s)}, hajm {g(s)}, bo'sh satrga nisbatan +{g(s) - g('')}")
matn = "a" * 1000
print(f"  1000 ta 'a': {g(matn)}   + bitta emoji: {g(matn + '🙂')}   ({g(matn + '🙂') / g(matn):.1f} barobar)")

print("\n=== 3. Konteynerlar: sig'im va zaxira ===")
royxat = []
oldingi = g(royxat)
qadamlar = [f"0→{oldingi}"]
for i in range(1, 33):
    royxat.append(i)
    if g(royxat) != oldingi:
        oldingi = g(royxat)
        qadamlar.append(f"{i}→{oldingi}")
print(f"  append bilan o'sish (uzunlik→bayt): {', '.join(qadamlar)}")
oniga = []
for i in range(10):
    oniga.append(i)
print(f"  [0] * 10: {g([0] * 10)},  list(range(10)): {g(list(range(10)))},  10 marta append: {g(oniga)}")
print(f"  tuple: () {g(())}, (1,) {g((1,))}, (1, 2) {g((1, 2))}")
print(f"  dict: {{}} {g({})}, 1 ta {g({'a': 1})}, 5 ta {g(dict.fromkeys(range(5)))}, 6 ta {g(dict.fromkeys(range(6)))}")
print(f"  set: set() {g(set())}, 4 ta {g(set(range(4)))}, 5 ta {g(set(range(5)))}")

print("\n=== 4. Elementlar hisobga kirmaydi ===")
kichik = [1, 2, 3]
katta = ["x" * 1_000_000, "y" * 1_000_000, "z" * 1_000_000]
print(f"  [1, 2, 3]: {g(kichik)},  3 ta millionlik satr ro'yxati: {g(katta)}")
print(f"  range(10): {g(range(10))},  range(10**12): {g(range(10**12))}")
print(f"  generator 10 ta: {g(x for x in range(10))},  10**9 ta: {g(x for x in range(10**9))}")

print("\n=== 5. GC sarlavhasi va __sizeof__ ===")
for obj in [[], {}, (1, 2), 12345, "salom"]:
    print(f"  {type(obj).__name__:6} getsizeof {g(obj):>3}, __sizeof__ {obj.__sizeof__():>3}, farq {g(obj) - obj.__sizeof__()}")


class Tasvir:
    """C kengaytma kabi: ma'lumotni o'zi boshqaradi va hajmini o'zi aytadi."""

    def __init__(self, en, boy):
        self._piksellar = bytearray(en * boy * 4)

    def __sizeof__(self):
        return object.__sizeof__(self) + sys.getsizeof(self._piksellar)


print(f"  o'z __sizeof__: Tasvir(100, 100) → {g(Tasvir(100, 100))} bayt")

Natijaning muhim qismi:

text
=== 1. int: har 30 bitga 4 bayt ===
    0 bit  → 28 bayt
    1 bit  → 28 bayt
   30 bit  → 28 bayt
   31 bit  → 32 bayt
   60 bit  → 32 bayt
   61 bit  → 36 bayt
  333 bit  → 72 bayt

=== 2. str: eng katta belgi hamma belgi hajmini belgilaydi ===
  ASCII    'Toshkent'   uzunlik 8, hajm 49, bo'sh satrga nisbatan +8
  latin-1  'Montréal'   uzunlik 8, hajm 65, bo'sh satrga nisbatan +24
  UCS-2    'Qarşı ş'    uzunlik 7, hajm 72, bo'sh satrga nisbatan +31
  UCS-4    'Salom 🙂'    uzunlik 7, hajm 88, bo'sh satrga nisbatan +47
  1000 ta 'a': 1041   + bitta emoji: 4064   (3.9 barobar)

=== 3. Konteynerlar: sig'im va zaxira ===
  append bilan o'sish (uzunlik→bayt): 0→56, 1→88, 5→120, 9→184, 17→248, 25→312
  [0] * 10: 136,  list(range(10)): 136,  10 marta append: 184
  tuple: () 48, (1,) 56, (1, 2) 64
  dict: {} 64, 1 ta 184, 5 ta 224, 6 ta 352
  set: set() 216, 4 ta 216, 5 ta 728

=== 4. Elementlar hisobga kirmaydi ===
  [1, 2, 3]: 88,  3 ta millionlik satr ro'yxati: 80
  range(10): 48,  range(10**12): 48
  generator 10 ta: 200,  10**9 ta: 200

=== 5. GC sarlavhasi va __sizeof__ ===
  list   getsizeof  56, __sizeof__  40, farq 16
  dict   getsizeof  64, __sizeof__  48, farq 16
  tuple  getsizeof  64, __sizeof__  48, farq 16
  int    getsizeof  28, __sizeof__  28, farq 0
  str    getsizeof  46, __sizeof__  46, farq 0
  o'z __sizeof__: Tasvir(100, 100) → 40105 bayt

Nima ko'rsatdi: 2.1–2.3-bo'limlar.

Misol 2 — Chuqur hajm va tracemalloc

python
"""Rekursiv chuqur hajm: ulashilgan obyektlar, sikllar, o'lmas obyektlar; natijani tracemalloc bilan solishtirish."""

import sys
import tracemalloc
import types

O_TKAZIB_YUBORISH = (type, types.ModuleType, types.FunctionType, types.BuiltinFunctionType)


def chuqur_hajm(obj, korilgan=None):
    if korilgan is None:
        korilgan = set()
    if id(obj) in korilgan or isinstance(obj, O_TKAZIB_YUBORISH) or sys._is_immortal(obj):
        return 0
    korilgan.add(id(obj))
    hajm = sys.getsizeof(obj)
    if isinstance(obj, (str, bytes, bytearray, int, float, complex)):
        return hajm
    if isinstance(obj, dict):
        hajm += sum(chuqur_hajm(k, korilgan) + chuqur_hajm(v, korilgan) for k, v in obj.items())
    elif isinstance(obj, (list, tuple, set, frozenset)):
        hajm += sum(chuqur_hajm(el, korilgan) for el in obj)
    for nom in getattr(type(obj), "__slots__", ()):
        if hasattr(obj, nom):
            hajm += chuqur_hajm(getattr(obj, nom), korilgan)
    if hasattr(obj, "__dict__"):
        hajm += chuqur_hajm(vars(obj), korilgan)
    return hajm


print("=== 1. Sayoz va chuqur ===")
satr = "".join(["a"] * 1000)
bir_xil = [satr] * 1000
turli = ["".join(["a"] * 1000) for _ in range(1000)]
print(f"  getsizeof:   bir xil satrlar {sys.getsizeof(bir_xil):>9,}   turli satrlar {sys.getsizeof(turli):>9,}")
print(f"  chuqur hajm: bir xil satrlar {chuqur_hajm(bir_xil):>9,}   turli satrlar {chuqur_hajm(turli):>9,}")

print("\n=== 2. Sikl va o'lmas obyektlar ===")
tugun = {"nom": "".join(["ild", "iz"]), "bolalar": []}
tugun["bolalar"].append(tugun)
print(f"  o'ziga havola qilgan lug'at: {chuqur_hajm(tugun)} bayt (cheksiz rekursiya yo'q)")
sonlar = list(range(200))
print(f"  0..199 ro'yxati: getsizeof {sys.getsizeof(sonlar)}, chuqur {chuqur_hajm(sonlar)} — kichik sonlar umumiy, hisoblanmadi")
katta_sonlar = [10**6 + i for i in range(200)]
print(f"  10**6.. ro'yxati: getsizeof {sys.getsizeof(katta_sonlar)}, chuqur {chuqur_hajm(katta_sonlar)} = {sys.getsizeof(katta_sonlar)} + 200 × {sys.getsizeof(10**6)}")

print("\n=== 3. Chuqur hajm va tracemalloc ===")


def sessiyalar_yarat(n):
    return {
        f"sess-{i:06d}": {
            "foydalanuvchi": f"user{i}@wisar.uz",
            "rollar": ["o'quvchi", "".join(["muallif", str(i % 3)])],
            "savat": [{"id": 10_000 + j, "narx": 49_900.0 + j} for j in range(i % 4)],
        }
        for i in range(n)
    }


tracemalloc.start()
kesh = sessiyalar_yarat(20_000)
haqiqiy, _ = tracemalloc.get_traced_memory()
tracemalloc.stop()
taxmin = chuqur_hajm(kesh)
print(f"  getsizeof(kesh): {sys.getsizeof(kesh) / 1e6:6.2f} MB")
print(f"  chuqur hajm:     {taxmin / 1e6:6.2f} MB")
print(f"  tracemalloc:     {haqiqiy / 1e6:6.2f} MB")
print(f"  getsizeof haqiqiyning {sys.getsizeof(kesh) / haqiqiy:.0%} ini ko'rsatdi; chuqur hajm {taxmin / haqiqiy:.0%} ini")

Natijaning muhim qismi:

text
=== 1. Sayoz va chuqur ===
  getsizeof:   bir xil satrlar     8,056   turli satrlar     8,856
  chuqur hajm: bir xil satrlar     9,097   turli satrlar 1,049,856

=== 2. Sikl va o'lmas obyektlar ===
  o'ziga havola qilgan lug'at: 366 bayt (cheksiz rekursiya yo'q)
  0..199 ro'yxati: getsizeof 1656, chuqur 1656 — kichik sonlar umumiy, hisoblanmadi
  10**6.. ro'yxati: getsizeof 1656, chuqur 7256 = 1656 + 200 × 28

=== 3. Chuqur hajm va tracemalloc ===
  getsizeof(kesh):   0.42 MB
  chuqur hajm:      17.40 MB
  tracemalloc:      17.52 MB
  getsizeof haqiqiyning 2% ini ko'rsatdi; chuqur hajm 99% ini

Nima ko'rsatdi: 2.4, 2.5-bo'limlar.

Misol 3 — Obyekt nusxalari va massivlar: getsizeof aldaydi

python
"""100 000 nusxa: oddiy klass, dataclass, slots, namedtuple, tuple, dict; float ro'yxati va array.array."""

import array
import gc
import sys
import tracemalloc
from collections import namedtuple
from dataclasses import dataclass


class Nuqta:
    def __init__(self, x, y):
        self.x = x
        self.y = y


@dataclass
class NuqtaDC:
    x: float
    y: float


class NuqtaSlots:
    __slots__ = ("x", "y")

    def __init__(self, x, y):
        self.x = x
        self.y = y


NuqtaNT = namedtuple("NuqtaNT", "x y")
N = 100_000
X, Y = 1.5, 2.5


def bir_nusxa(yasovchi):
    gc.collect()
    tracemalloc.start()
    nusxalar = [yasovchi() for _ in range(N)]
    jami, _ = tracemalloc.get_traced_memory()
    tracemalloc.stop()
    return sys.getsizeof(nusxalar[0]), (jami - sys.getsizeof(nusxalar)) / N


print("=== 1. Bitta nusxa: getsizeof va haqiqiy ===")
print(f"  {'tur':12} {'getsizeof':>9} {'tracemalloc':>12}")
for nom, yasovchi in [("klass", lambda: Nuqta(X, Y)), ("dataclass", lambda: NuqtaDC(X, Y)),
                      ("__slots__", lambda: NuqtaSlots(X, Y)), ("namedtuple", lambda: NuqtaNT(X, Y)),
                      ("tuple", lambda: tuple([X, Y])), ("dict", lambda: {"x": X, "y": Y})]:
    sayoz, haqiqiy = bir_nusxa(yasovchi)
    print(f"  {nom:12} {sayoz:>9} {haqiqiy:>12.0f}")

print("\n=== 2. __dict__ so'ralganda nima bo'ladi ===")
nuqtalar = [Nuqta(X, Y) for _ in range(N)]
gc.collect()
tracemalloc.start()
for n in nuqtalar:
    n.__dict__
qoshimcha, _ = tracemalloc.get_traced_memory()
tracemalloc.stop()
print(f"  {N:,} nusxada __dict__ ga murojaat: +{qoshimcha / N:.0f} bayt har biriga, getsizeof(nusxa) esa hamon {sys.getsizeof(nuqtalar[0])}")
del nuqtalar

print("\n=== 3. float ro'yxati va array.array ===")
gc.collect()
tracemalloc.start()
royxat = [float(i) for i in range(N)]
royxat_haqiqiy, _ = tracemalloc.get_traced_memory()
tracemalloc.stop()
tracemalloc.start()
massiv = array.array("d", (float(i) for i in range(N)))
massiv_haqiqiy, _ = tracemalloc.get_traced_memory()
tracemalloc.stop()
print(f"  list[float]:      getsizeof {sys.getsizeof(royxat) / 1e6:.2f} MB, haqiqiy {royxat_haqiqiy / 1e6:.2f} MB")
print(f"  array('d'):       getsizeof {sys.getsizeof(massiv) / 1e6:.2f} MB, haqiqiy {massiv_haqiqiy / 1e6:.2f} MB")
print(f"  getsizeof bo'yicha ular deyarli teng, haqiqatda ro'yxat {royxat_haqiqiy / massiv_haqiqiy:.1f} barobar katta")
print(f"  sabab: ro'yxatda 8 baytli ko'rsatkich + har biri {sys.getsizeof(1.5)} baytli float obyekti; massivda 8 baytli raw qiymat")

Natijaning muhim qismi:

text
=== 1. Bitta nusxa: getsizeof va haqiqiy ===
  tur          getsizeof  tracemalloc
  klass               48           88
  dataclass           48           88
  __slots__           48           48
  namedtuple          64           72
  tuple               64           64
  dict               184          184

=== 2. __dict__ so'ralganda nima bo'ladi ===
  100,000 nusxada __dict__ ga murojaat: +64 bayt har biriga, getsizeof(nusxa) esa hamon 48

=== 3. float ro'yxati va array.array ===
  list[float]:      getsizeof 0.80 MB, haqiqiy 3.20 MB
  array('d'):       getsizeof 0.82 MB, haqiqiy 0.82 MB
  getsizeof bo'yicha ular deyarli teng, haqiqatda ro'yxat 3.9 barobar katta
  sabab: ro'yxatda 8 baytli ko'rsatkich + har biri 24 baytli float obyekti; massivda 8 baytli raw qiymat

Nima ko'rsatdi: 2.5, 2.6-bo'limlar.

Misol 4 — Amaliy: xotira byudjetli kesh

Sessiyalar keshi uchun yozuvlar sonini emas, xotira hajmini cheklaymiz. Har yozuv qo'shilganda chuqur hajm taxmini hisoblanadi; byudjetdan oshsa, eng eski yozuvlar chiqariladi. So'ng taxminni tracemalloc bilan tekshiramiz.

python
"""OrderedDict asosidagi LRU kesh: byudjet baytlarda, har yozuv chuqur hajm bilan; taxmin va tracemalloc solishtiriladi."""

import sys
import tracemalloc
from collections import OrderedDict


def chuqur_hajm(obj, korilgan=None, internlarni_sana=True):
    if korilgan is None:
        korilgan = set()
    if id(obj) in korilgan or sys._is_immortal(obj):
        return 0
    if not internlarni_sana and isinstance(obj, str) and sys._is_interned(obj):
        return 0
    korilgan.add(id(obj))
    hajm = sys.getsizeof(obj)
    ichki = lambda x: chuqur_hajm(x, korilgan, internlarni_sana)
    if isinstance(obj, dict):
        hajm += sum(ichki(k) + ichki(v) for k, v in obj.items())
    elif isinstance(obj, (list, tuple, set)):
        hajm += sum(ichki(el) for el in obj)
    return hajm


class HajmliKesh:
    def __init__(self, byudjet_bayt):
        self.byudjet = byudjet_bayt
        self._malumot = OrderedDict()
        self._hajmlar = {}
        self.jami = 0
        self.chiqarilgan = 0

    def qoy(self, kalit, qiymat):
        if kalit in self._malumot:
            self._olib_tashla(kalit)
        hajm = chuqur_hajm(kalit) + chuqur_hajm(qiymat)
        if hajm > self.byudjet:
            raise ValueError(f"{kalit!r}: bitta yozuv ({hajm} bayt) byudjetdan katta")
        self._malumot[kalit] = qiymat
        self._hajmlar[kalit] = hajm
        self.jami += hajm
        while self.jami > self.byudjet:
            eski = next(iter(self._malumot))
            self._olib_tashla(eski)
            self.chiqarilgan += 1

    def ol(self, kalit):
        self._malumot.move_to_end(kalit)
        return self._malumot[kalit]

    def _olib_tashla(self, kalit):
        del self._malumot[kalit]
        self.jami -= self._hajmlar.pop(kalit)

    def __len__(self):
        return len(self._malumot)


def sessiya(i):
    return {
        "foydalanuvchi": f"user{i}@wisar.uz",
        "tarix": [f"/kurs/python/{j}" for j in range(i % 20)],
        "sozlamalar": {"til": "".join(["u", "z"]), "tema": "".join(["qorong'", "i"])},
    }


print("=== 1. Byudjet 2 MB, 10 000 sessiya ===")
tracemalloc.start()
kesh = HajmliKesh(byudjet_bayt=2_000_000)
for i in range(10_000):
    kesh.qoy(f"sess-{i:06d}", sessiya(i))
haqiqiy, _ = tracemalloc.get_traced_memory()
tracemalloc.stop()
print(f"  keshda: {len(kesh):,} yozuv, chiqarilgan: {kesh.chiqarilgan:,}")
print(f"  taxminiy jami: {kesh.jami / 1e6:.2f} MB (byudjet {kesh.byudjet / 1e6:.2f} MB)")
print(f"  tracemalloc (kesh + ichki jadvallar): {haqiqiy / 1e6:.2f} MB")
print(f"  taxmin haqiqiyning {kesh.jami / haqiqiy:.0%} i — taxmin haqiqiydan KATTA chiqdi")

print("\n=== 2. Nega katta: ulashilgan satrlar har yozuvda qayta sanaldi ===")
kalit_nomlari = ["foydalanuvchi", "tarix", "sozlamalar", "til", "tema"]
print(f"  lug'at kalitlari kod konstantalari — interned: {all(sys._is_interned(k) for k in kalit_nomlari)}")
bir_yozuvda = sum(sys.getsizeof(k) for k in kalit_nomlari)
print(f"  ular har yozuvda {bir_yozuvda} bayt deb sanalgan, aslida hamma yozuv uchun bitta nusxa: "
      f"{bir_yozuvda * len(kesh) / 1e6:.2f} MB ortiqcha")
tuzatilgan = sum(chuqur_hajm(k, internlarni_sana=False) + chuqur_hajm(v, internlarni_sana=False)
                 for k, v in kesh._malumot.items())
jadvallar = sys.getsizeof(kesh._malumot) + sys.getsizeof(kesh._hajmlar)
print(f"  internlarsiz taxmin: {tuzatilgan / 1e6:.2f} MB + OrderedDict va _hajmlar jadvallari {jadvallar / 1e6:.2f} MB"
      f" = {(tuzatilgan + jadvallar) / 1e6:.2f} MB, haqiqiyning {(tuzatilgan + jadvallar) / haqiqiy:.0%} i")

print("\n=== 3. Haddan katta yozuv ===")
bir_xil = {"tarix": ["x" * 1000 for _ in range(3000)]}
turli = {"tarix": ["x" * 1000 + str(i) for i in range(3000)]}
print(f"  'x' * 1000 — oldindan hisoblangan bitta konstanta, 3000 havola: {chuqur_hajm(bir_xil) / 1e6:.2f} MB")
try:
    kesh.qoy("ulkan", turli)
except ValueError as xato:
    print(f"  ValueError: {xato}")

Natijaning muhim qismi:

text
=== 1. Byudjet 2 MB, 10 000 sessiya ===
  keshda: 1,341 yozuv, chiqarilgan: 8,659
  taxminiy jami: 2.00 MB (byudjet 2.00 MB)
  tracemalloc (kesh + ichki jadvallar): 1.90 MB
  taxmin haqiqiyning 105% i — taxmin haqiqiydan KATTA chiqdi

=== 2. Nega katta: ulashilgan satrlar har yozuvda qayta sanaldi ===
  lug'at kalitlari kod konstantalari — interned: True
  ular har yozuvda 240 bayt deb sanalgan, aslida hamma yozuv uchun bitta nusxa: 0.32 MB ortiqcha
  internlarsiz taxmin: 1.68 MB + OrderedDict va _hajmlar jadvallari 0.18 MB = 1.86 MB, haqiqiyning 98% i

=== 3. Haddan katta yozuv ===
  'x' * 1000 — oldindan hisoblangan bitta konstanta, 3000 havola: 0.03 MB
  ValueError: 'ulkan': bitta yozuv (3160206 bayt) byudjetdan katta

Nima ko'rsatdi: 2.4, 2.5-bo'limlar — yozuvlarni alohida o'lchaganda ulashilgan obyektlar (interned kalit nomlari) har yozuvda qayta sanaladi va taxmin oshib ketadi; ularni chiqarib, keshning o'z ichki jadvallarini qo'shganda taxmin haqiqiyga yaqinlashadi. Byudjet uchun biroz ortiqcha taxmin xavfsiz tomonda xato qiladi.


5. To'g'ri va noto'g'ri tushunishlar

Noto'g'ri fikr To'g'risi
"getsizeof(royxat) — ro'yxat va elementlari hajmi" Faqat ko'rsatkichlar jadvali
"Bir xil uzunlikdagi satrlar bir xil hajmda" Eng katta belgiga bog'liq: 1, 2 yoki 4 bayt/belgi
"int har doim 28 bayt" Har 30 bitga +4 bayt, cheksiz o'sadi
"range(10**9) katta joy oladi" 48 bayt — elementlarni saqlamaydi
"getsizeof(obj) atributlarni ham o'lchaydi" 3.13+ da inline atributlar kirmaydi
"list[float] va array('d') bir xil" Ro'yxat ~4 barobar katta: har float alohida obyekt
"tracemalloc = jarayon hajmi" Faqat Python ajratuvchisi; RSS boshqa
"Chuqur hajm — aniq qiymat" Ulashilgan obyektlar tufayli taxmin

6. Keng tarqalgan xatolar va yechimlari

1. Keshni getsizeof bilan o'lchash

python
if sys.getsizeof(kesh) > LIMIT: tozala()     # ❌ elementlar hisoblanmaydi

Chuqur hajm taxmini yoki yozuv hajmlarini alohida yig'ish (Misol 4).

2. Chuqur hajmda ko'rilganlar to'plamisiz rekursiya

python
def hajm(o):
    return sys.getsizeof(o) + sum(hajm(x) for x in o)   # ❌ sikl — RecursionError, ulashilganlar ikki marta

id bo'yicha korilgan to'plami.

3. Klass va modullarga kirib ketish

python
hajm += chuqur_hajm(type(obj))     # ❌ butun tur tizimi, modullar hisobga kiradi

type, ModuleType, FunctionType ni o'tkazib yuborish.

4. Obyektlarni getsizeof bilan solishtirib, tuzilma tanlash

python
sys.getsizeof(Nuqta(1, 2)) == sys.getsizeof(NuqtaSlots(1, 2))   # 48 == 48 — "farqi yo'q" ❌

tracemalloc bilan ko'p nusxa yaratib, bir nusxaga bo'lish.

5. Katta raqamli ma'lumotni ro'yxatda saqlash

python
qiymatlar = [float(x) for x in oqim]          # ⚠️ har element ~32 bayt
qiymatlar = array.array("d", map(float, oqim)) # ✅ 8 bayt; yoki NumPy (24-qism)

6. Satrga tasodifiy emoji qo'shilishi

python
hisobot = "\n".join(qatorlar) + " ✅"   # ⚠️ butun hisobot 4 bayt/belgi bo'ldi

Katta matnli ma'lumotda belgilar to'plamini bilish; kerak bo'lsa bytes sifatida (UTF-8) saqlash.

7. tracemalloc ni yoqib qo'yib, production da unutish

python
tracemalloc.start()        # ❌ butun servis sekinlashadi va qo'shimcha xotira oladi

Faqat tahlil vaqtida yoki alohida endpoint/signal orqali qisqa muddatga.

8. __sizeof__ da GC sarlavhasini qo'shish

python
def __sizeof__(self):
    return 16 + object.__sizeof__(self) + ...  # ❌ getsizeof uni o'zi qo'shadi

7. Integratsiya — bu bilim qayerda kerak bo'ladi

  • 4.12-dars (o'tilgan): satrlarning o'zgarmasligi va ichki tuzilishi
  • 6.4-dars (o'tilgan): ro'yxat va xotira — zaxira joy
  • 11.5-dars (o'tilgan): sikllar — chuqur hajm ularni hisobga oladi
  • 11.7-dars (o'tilgan): interned satrlar va kichik sonlar — umumiy obyektlar
  • 11.9-dars: __slots__ xotirani qanday tejaydi
  • 12.4-dars: PyObject — obyekt sarlavhasi C darajasida
  • 24.1-dars: NumPy massivlari — nbytes va raw ma'lumot
  • 29.9-dars: xotira optimizatsiyasi — RSS, fragmentatsiya, xotira sizishini topish

8. Eng yaxshi amaliyotlar

  1. getsizeof — faqat bitta obyektning sayoz hajmi uchun.

  2. Konteyner hajmini chuqur hajm bilan taxmin qiling — ko'rilganlar to'plami, o'lmaslar va klasslarsiz.

  3. Tuzilmalarni tracemalloc bilan solishtiring — ko'p nusxa, bir nusxaga bo'lish.

  4. Kesh va buferlarni yozuvlar soni emas, bayt byudjeti bilan cheklang (kerak bo'lsa).

  5. Raqamli ma'lumot uchun array yoki NumPy.

  6. Satr ko'rinishini (ASCII / UCS-4) hisobga oling.

  7. tracemalloc ni qisqa muddatga yoqing.

  8. Jarayon darajasidagi xotirani alohida kuzating (RSS, konteyner metrikalari).


9. Amaliy topshiriq

Vazifa 1: Natijani bashorat qiling

python
import sys
g = sys.getsizeof
1.  print(g([1, 2, 3]) == g(["x" * 1000, "y", "z"]))
2.  print(g(range(10)) == g(range(10**9)))
3.  print(g("abc") - g("ab"))
4.  print(g("şşş") - g("şş"))
5.  print(g(2**30) - g(2**30 - 1))
6.  royxat = []
    royxat.append(1)
    print(g(royxat) - g([]))
7.  print(g((1, 2, 3)) < g([1, 2, 3]))
8.  d = {}
    bosh = g(d)
    d["a"] = 1
    print(g(d) > bosh)
9.  class K:
        def __sizeof__(self):
            return 100
    print(g(K()) > 100)
10. print(g(x for x in range(10)) == g(x for x in range(10**6)))
11. print(g(True) == g(1))
12. a = [0] * 100
    b = a[:50]
    print(g(b) < g(a))
Javoblar
  1. True — ikkala ro'yxat ham 3 ta ko'rsatkich
  2. True — range faqat start, stop, step saqlaydi
  3. 1 — ASCII, belgi boshiga 1 bayt
  4. 2 — UCS-2
  5. 4 — ikkinchi 30 bitlik raqam
  6. 32 — birinchi append 4 ta joy ajratadi
  7. True — kortejda zaxira joy va ro'yxatdagi qo'shimcha maydonlar yo'q
  8. True — bo'sh lug'at jadvalsiz, birinchi element jadval yaratadi
  9. True — getsizeof GC sarlavhasi va obyekt oldi maydonlarini qo'shadi
  10. True — generator elementlarni saqlamaydi
  11. True — bool ham bitta raqamli int
  12. True — kesma aniq 50 ta joy bilan yaratiladi

Vazifa 2: Xatolarni tuzating

python
1.  def kesh_hajmi(kesh):
        return sys.getsizeof(kesh)

2.  def chuqur(o):
        jami = sys.getsizeof(o)
        if isinstance(o, (list, dict)):
            for x in (o.values() if isinstance(o, dict) else o):
                jami += chuqur(x)
        return jami

3.  def eng_yengil(yasovchilar):
        return min(yasovchilar, key=lambda f: sys.getsizeof(f()))

4.  class Rasm:
        def __init__(self, data): self.data = data
        def __sizeof__(self): return sys.getsizeof(self) + len(self.data)

5.  tracemalloc.start()
    malumot = yukla()
    print(tracemalloc.get_traced_memory())
Javoblar
python
1.  def kesh_hajmi(kesh):
        return chuqur_hajm(kesh)                    # elementlar va ichki obyektlar ham

2.  def chuqur(o, korilgan=None):
        korilgan = set() if korilgan is None else korilgan
        if id(o) in korilgan:
            return 0                                # sikl va ulashilgan obyektlar
        korilgan.add(id(o))
        jami = sys.getsizeof(o)
        if isinstance(o, dict):
            jami += sum(chuqur(k, korilgan) + chuqur(v, korilgan) for k, v in o.items())   # kalitlar ham
        elif isinstance(o, (list, tuple, set)):
            jami += sum(chuqur(x, korilgan) for x in o)
        return jami

3.  def eng_yengil(yasovchilar, n=10_000):
        def olch(f):
            tracemalloc.start()
            nusxalar = [f() for _ in range(n)]
            jami = tracemalloc.get_traced_memory()[0]
            tracemalloc.stop()
            return jami
        return min(yasovchilar, key=olch)           # getsizeof inline atributlarni ko'rmaydi

4.  class Rasm:
        def __init__(self, data): self.data = data
        def __sizeof__(self): return object.__sizeof__(self) + sys.getsizeof(self.data)   # getsizeof(self) → cheksiz rekursiya

5.  tracemalloc.start()
    malumot = yukla()
    print(tracemalloc.get_traced_memory())
    tracemalloc.stop()                              # to'xtatilmasa dastur sekin qoladi

Vazifa 3: Hajm jadvali

Skript yozing:

  1. int uchun 0 dan 2**300 gacha har 30 bitda hajmni chiqarib, formulasini tekshirsin
  2. Satr uchun to'rt ko'rinishning har birida 0–10 uzunlikdagi hajmlarni chiqarib, sarlavha va belgi hajmini aniqlasin
  3. list, dict, set uchun 0–100 elementgacha o'sish nuqtalarini topsin
  4. Natijani Markdown jadvalga yozsin

Vazifa 4: To'liq chuqur_hajm

Misol 2 dagi funksiyani kengaytiring:

  1. __slots__ meros bo'lganda barcha ota klasslarning slotlarini hisobga olsin
  2. deque, array, defaultdict, bytearray, memoryview ni qo'llab-quvvatlasin
  3. vars() chaqirmasdan, 3.13+ obyektlar uchun gc.get_referents asosida ishlaydigan variant yozing
  4. hisobot=True bo'lsa, turlar bo'yicha hajmni Counter ko'rinishida qaytarsin
  5. Natijani tracemalloc bilan 5 xil ma'lumot to'plamida solishtiring

Vazifa 5: Tuzilma tanlash

1 million yozuvli "buyurtma" (id, foydalanuvchi_id, summa, holat, vaqt) uchun:

  1. dict, oddiy klass, dataclass, dataclass(slots=True), namedtuple, tuple, ustunli ko'rinish (har maydon uchun alohida array) variantlarini yarating
  2. Har biri uchun tracemalloc bo'yicha jami xotira va yaratish vaqtini o'lchang
  3. holat maydonini sys.intern qilishning ta'sirini qo'shing (11.7-dars)
  4. Natijani jadvalda ko'rsatib, qaysi holatda qaysi variantni tanlashni tushuntiring

Vazifa 6: Xotira monitori

XotiraMonitori kontekst menejerini yozing (10.6-dars):

  1. Kirishda tracemalloc.start() va snapshot
  2. Chiqishda ikkinchi snapshot, compare_to bilan eng ko'p xotira ajratgan 10 ta qatorni chiqarsin
  3. Eng yuqori (peak) qiymatni ham ko'rsatsin
  4. Dekorator sifatida ham ishlasin (10.8-dars)
  5. Misol 4 dagi keshni to'ldirish jarayoniga qo'llang

Vazifa 7: O'ylash

Java'da obyekt hajmini bilish uchun Instrumentation.getObjectSize (sayoz) va JOL kutubxonasi bor, Go'da unsafe.Sizeof kompilyatsiya vaqtida hisoblanadi, C++ da sizeof va Rust'da std::mem::size_of ham shunday. Nega statik tipli tillarda obyekt hajmi ko'pincha kompilyatsiya vaqtida ma'lum, Python'da esa hatto "chuqur hajm" ham taxmin? Bu farq xotira bo'yicha qaror qabul qilishga qanday ta'sir qiladi?

Javob

Qisqa javob: statik tipli tillarda tur ma'lumotning joylashuvini (layout) belgilaydi: struct {int32; float64} — kompilyator maydonlar hajmi va tekislashni biladi, qiymatlar ko'pincha to'g'ridan-to'g'ri (inline) saqlanadi. Python'da har bir qiymat — alohida obyekt, konteynerlar faqat ko'rsatkich saqlaydi, obyektlar ulashiladi va turlar ish vaqtida o'zgaradi. Shuning uchun "bu obyekt qancha joy oladi" savolining o'zi Python'da noaniq.

1. C++ va Rust

cpp
struct Nuqta { double x; double y; };   // sizeof(Nuqta) == 16
std::vector<Nuqta> v(1'000'000);        // ~16 MB — ketma-ket joylashgan
rust
std::mem::size_of::<Nuqta>()            // 16, kompilyatsiya vaqtida
  • Qiymat semantikasi: Nuqta massivda o'zi turadi, ko'rsatkich emas
  • Heap dagi ma'lumot (std::string, Vec) sizeof ga kirmaydi — ular ham "sayoz" (Python'dagi kabi), lekin bu aniq ko'rinadi

2. Go

  • unsafe.Sizeof — kompilyatsiya vaqtida, sayoz
  • Struct va massivlar qiymat sifatida saqlanadi; slice, map, string — sarlavha va ko'rsatkich

3. Java

  • Primitivlar (int, double) inline, obyektlar esa havola orqali — Python'ga yaqinroq
  • Instrumentation.getObjectSize — sayoz; JOL maydonlar joylashuvi va tekislashni ko'rsatadi
  • ArrayList<Double> va double[] farqi — Python'dagi list[float] va array('d') farqining aynan o'zi
  • Valhalla loyihasi (qiymat klasslari) aynan shu muammoni hal qilishga qaratilgan

4. Python

Xususiyat Natija
Har qiymat — obyekt (sarlavha 16+ bayt) float 24 bayt, C'da 8
Konteynerlar faqat ko'rsatkich Sayoz hajm hech narsa demaydi
Obyektlar ulashiladi (kichik sonlar, interned satrlar) "Kimga tegishli" noaniq
Dinamik atributlar, __dict__, inline values Hajm tur bilan aniqlanmaydi
C kengaytmalar O'z __sizeof__ iga bog'liq

5. Qarorlarga ta'siri

  1. Statik tillarda hajmni kodni o'qib hisoblash mumkin; Python'da o'lchash kerak (tracemalloc)
  2. Python'da katta ma'lumot uchun ustunli, qiymatlarni inline saqlaydigan tuzilmalarga o'tish — array, NumPy, pandas, Arrow — C/Rust yondashuvini qaytaradi
  3. Obyekt sonini kamaytirish (__slots__, kortejlar, interning) — sarlavha narxini kamaytirish
  4. Katta tizimda xotira byudjetini jarayon darajasida (RSS, konteyner chegarasi) kuzatish — hech bir til ichidagi o'lchov to'liq emas

6. Xulosa

  1. Statik tillarda tur maydonlar joylashuvini belgilaydi — hajm kompilyatsiyada ma'lum (sayoz)
  2. Python'da har qiymat obyekt, konteyner ko'rsatkich saqlaydi, obyektlar ulashiladi — chuqur hajm taxmin
  3. Java'dagi ArrayList<Double> va double[] farqi Python'dagi list va array farqiga teng
  4. Python'da xotira bo'yicha qaror — o'lchov asosida va raqamli ma'lumot uchun inline massivlarga o'tish orqali

Nimani mustahkamlaydi: 2.1–2.6-bo'limlar.


Xulosa

Bu darsda Python obyektlarining haqiqiy hajmini o'lchashni o'rgandik.

Eng muhim uch fikr:

  1. getsizeof — sayoz o'lchov. U obyektning o'ziga ajratilgan xotira va GC sarlavhasini qaytaradi: int 28 bayt (+4 har 30 bitga), satr eng katta belgisiga qarab 1, 2 yoki 4 bayt/belgi, ro'yxat 56 + 8 × sig'im. Konteyner elementlari, 3.13+ obyektlarning inline atributlari va klasslar hisobga kirmaydi — shuning uchun getsizeof(kesh) haqiqiy hajmning kichik qismini ko'rsatadi.

  2. Chuqur hajm — taxmin. Obyektlar grafini aylanib chiqish uchun ko'rilganlar to'plami (ulashilgan obyektlar va sikllar), o'lmas obyektlarni va klass/modullarni o'tkazib yuborish kerak. Natija byudjet va monitoring uchun yetarli, lekin "kimga tegishli" savoli tufayli aniq emas.

  3. tracemalloc — haqiqiy ajratilgan xotira. Tuzilmalarni solishtirish uchun ko'p nusxa yaratib, tracemalloc bilan o'lchang: oddiy klass nusxasi getsizeof bo'yicha 48, haqiqatda ~88 bayt; list[float] array('d') dan ~4 barobar katta. tracemalloc ham jarayon hajmi (RSS) emas va kodni sekinlashtiradi — qisqa muddatga yoqing.

Keyingi darsda __slots__ ni chuqur o'rganamiz: u obyekt tuzilishini qanday o'zgartiradi, xotirani qancha tejaydi, meros va weakref bilan qanday cheklovlari bor.

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
11.8-dars: sys.getsizeof va haqiqiy hajm — IlmHamroh