Mundarija (20)
- 1. Kirish va motivatsiya
- 2. Nazariya — chuqur tushuntirish
- 2.1. Sikllar qayerdan paydo bo'ladi
- 2.2. Sikl yig'uvchi qanday ishlaydi
- 2.3. Avlodlar va avtomatik yig'ish
- 2.4. gc moduli
- 2.5. Sikl ichidagi __del__
- 2.6. GC ni qachon sozlash kerak
- 3. Tez ma'lumotnoma
- 4. Batafsil misollar
- Misol 1 — Havolalar hisobi yetmaydigan sikllar
- Misol 2 — Algoritmni qo'lda bajarish
- Misol 3 — Avlodlar, chegaralar va freeze
- Misol 4 — Amaliy: sikllarsiz daraxt
- 5. To'g'ri va noto'g'ri tushunishlar
- 6. Keng tarqalgan xatolar va yechimlari
- 7. Integratsiya — bu bilim qayerda kerak bo'ladi
- 8. Eng yaxshi amaliyotlar
- 9. Amaliy topshiriq
- Xulosa
11.5-dars: Axlat yig'uvchi (GC) va sikllar
11-QISM — OBYEKT MODELI VA XOTIRA · 5-dars
1. Kirish va motivatsiya
11.4-darsda CPython obyektni havolalar soni nolga tushgan zahoti o'chirishini ko'rdik. Lekin bu mexanizm bitta holatda ojiz: obyektlar bir-biriga ishora qilsa.
class Tugun:
def __init__(self, nom, ota=None):
self.nom = nom
self.ota = ota
self.bolalar = []
if ota:
ota.bolalar.append(self)
ildiz = Tugun("html")
body = Tugun("body", ildiz)
del ildiz, body # ildiz → body (bolalar), body → ildiz (ota) — sikldel dan keyin ikkala tugunga ham tashqaridan havola yo'q, lekin har birining havolalar soni 1: ular bir-birini ushlab turadi. Havolalar hisobi ularni hech qachon o'chirmaydi.
Real hodisa: HTML parser har sahifa uchun shunday daraxt quradi. Servis gc.disable() bilan "tezlashtirilgan" edi — bir necha soatda xotira tugadi. Boshqa servisda esa aksincha: GC yoqilgan, lekin har bir necha soniyada 200 ms lik to'xtashlar paydo bo'ldi — sababi millionlab kichik obyektlarni qayta-qayta tekshiruvchi to'liq yig'ish edi.
CPython'da ikkita mexanizm birga ishlaydi: havolalar hisobi — ko'p obyektni darhol o'chiradi, sikl yig'uvchi (gc) — faqat bir-biriga ishora qiluvchi konteynerlar guruhini vaqti-vaqti bilan topadi. Ikkinchisining qanday ishlashi, qachon ishga tushishi va uni qachon sozlash kerakligini bilish — xotira va kechikish muammolarini hal qilishning kaliti.
Bu darsda:
- Sikllar qayerdan paydo bo'ladi — ko'rinadigan va yashirin sikllar
- Sikl yig'uvchi qanday ishlaydi: ichki havolalarni ayirish algoritmi
- Avlodlar, chegaralar va avtomatik yig'ish
gcmoduli:collect,disable,freeze,callbacks,get_stats- Sikl ichidagi
__del__(PEP 442) vagc.garbage - Sikllarni oldini olish:
weakref, aniq tozalash, istisnolar
2. Nazariya — chuqur tushuntirish
2.1. Sikllar qayerdan paydo bo'ladi
| Manba | Sikl |
|---|---|
| Ota-bola havolalari | bola.ota = ota, ota.bolalar.append(bola) |
| Ikki tomonlama bog'langan ro'yxat | tugun.oldingi, tugun.keyingi |
| Graf tugunlari | Qo'shnilar bir-biriga ishora qiladi |
| O'ziga havola | obj.men = obj, royxat.append(royxat) |
| Istisno lokal o'zgaruvchida | xato = e → e.__traceback__ → freym → xato |
| Bog'langan metod atributda | self.ishlovchi = self.metod → __self__ → self |
| O'zini chaqiruvchi ichki funksiya | def yur(): yur() → hujayra → funksiya (10.12-dars) |
| Klass va uning metodlari | Funksiya → __globals__ → modul lug'ati → klass |
Yashirin sikllar () ayniqsa xavfli: kodda hech qanday "ota/bola" ko'rinmaydi.
2.2. Sikl yig'uvchi qanday ishlaydi
GC faqat konteyner obyektlarni kuzatadi — boshqa obyektlarga havola saqlay oladiganlarni (list, dict, klass nusxalari, funksiyalar...). int, str, float kuzatilmaydi (gc.is_tracked). Faqat atomar qiymatlardan tuzilgan tuple esa yaratilishida yoki birinchi GC tekshiruvida kuzatuvdan chiqariladi — shuning uchun gc.is_tracked natijasi uning qanday yaratilganiga qarab farq qilishi mumkin.
Bir avlodni yig'ish algoritmi (soddalashtirilgan):
- Har kuzatilayotgan obyekt uchun
gc_refs = ob_refcntnusxasi olinadi - Har obyektning ichidagi havolalari bo'ylab yuriladi — ishora qilingan obyektning
gc_refsi 1 ga kamaytiriladi - Endi
gc_refs > 0bo'lgan obyektlarga guruh tashqarisidan havola bor — ular va ulardan erishiladigan hamma narsa tirik - Qolganlari — faqat bir-birini ushlab turuvchi erishib bo'lmaydigan guruh
- Ular uchun: zaif havolalar tozalanadi,
__del__chaqiriladi (bir marta), keyin ichki havolalar uziladi — havolalar hisobi ularni o'chiradi
tashqi nom ──► A ◄──► B C ◄──► D
(tashqaridan havola yo'q)
A: refcnt 2, ichki 1 → gc_refs 1 → tirik (B ham tirik)
C: refcnt 1, ichki 1 → gc_refs 0 → erishib bo'lmaydi2.3. Avlodlar va avtomatik yig'ish
"Ko'p obyektlar yosh o'ladi" gipotezasi asosida uch avlod:
| Avlod | Kim | Qachon yig'iladi (Python 3.14, sukut) |
|---|---|---|
| 0 | Yangi yaratilgan konteynerlar | Ajratishlar − o'chirishlar > 2000 |
| 1 | 0-avloddan omon qolganlar | 0-avlod 10 marta yig'ilgach |
| 2 | 1-avloddan omon qolganlar | 1-avlod 10 marta yig'ilgach (+ qo'shimcha shart) |
- Yig'ishdan omon qolgan obyekt keyingi avlodga o'tadi
- 2-avlodni (to'liq) yig'ish uzoq yashaydigan obyektlar ko'paygandagina ishga tushadi — millionlab eski obyektlarni tez-tez tekshirmaslik uchun
gc.get_count()— joriy hisoblagichlar,gc.get_threshold()— chegaralar
Avtomatik yig'ish ixtiyoriy konteyner ajratilganda boshlanishi mumkin — ya'ni dasturning ixtiyoriy joyida, ixtiyoriy ipda. To'liq yig'ish katta xotirada sezilarli pauza beradi.
2.4. gc moduli
| Funksiya | Nima qiladi |
|---|---|
gc.collect(avlod=2) |
Qo'lda yig'ish; topilgan erishib bo'lmaydigan obyektlar sonini qaytaradi |
gc.disable() / gc.enable() / gc.isenabled() |
Avtomatik yig'ishni o'chirish/yoqish (havolalar hisobi ishlayveradi) |
gc.get_threshold() / gc.set_threshold(a, b, c) |
Chegaralar |
gc.get_count() |
Avlod hisoblagichlari |
gc.get_stats() |
Har avlod: yig'ishlar, yig'ilgan, yig'ilmaydigan |
gc.callbacks |
Yig'ish boshlanishi/tugashida chaqiriladigan funksiyalar ro'yxati |
gc.freeze() / gc.unfreeze() |
Hozirgi barcha obyektlarni "doimiy avlodga" o'tkazish — ular yig'ishda tekshirilmaydi |
gc.get_referrers, gc.get_referents |
Havolalar grafi (11.4-dars) |
gc.is_tracked(obj), gc.is_finalized(obj) |
Kuzatiladimi, __del__ chaqirilganmi |
gc.garbage, gc.set_debug(gc.DEBUG_SAVEALL) |
Tuzatish: yig'ilgan obyektlarni o'chirmasdan saqlash |
2.5. Sikl ichidagi __del__
Python 3.4 dan (PEP 442) __del__ li obyektlar sikli ham yig'iladi: avval hammasining __del__ i chaqiriladi, keyin, agar hech biri tirilmagan bo'lsa, sikl uziladi. Tartib — aniqlanmagan.
gc.garbage endi odatda bo'sh. U faqat C kengaytmalardagi ba'zi obyektlar yoki DEBUG_SAVEALL bilan to'ladi.
Sikldagi __del__ ichida boshqa sikl a'zolariga murojaat qilish xavfli: ular allaqachon "tozalanib" bo'lishi mumkin.
2.6. GC ni qachon sozlash kerak
| Vaziyat | Chora |
|---|---|
| Sikllar yo'q, lekin ajratish ko'p (ma'lumot yuklash) | Yuklash davomida gc.disable(), keyin gc.enable() va gc.collect() |
| Ko'p uzoq yashaydigan obyektlar (katta keshlar, modellar) | Yuklangach gc.freeze() — to'liq yig'ish ularni qayta tekshirmaydi |
fork qiluvchi serverlar (gunicorn preload) |
Ishga tushgach gc.freeze() — GC eski obyektlar sahifalariga yozmaydi, copy-on-write saqlanadi |
| Kechikishga sezgir yo'l | Chegaralarni oshirish; yig'ishni boshqariladigan paytga surish (gc.disable + davriy collect) |
| Tushunarsiz xotira o'sishi | gc.callbacks bilan yig'ishlarni kuzatish; sikl manbalarini weakref bilan uzish |
gc.disable() ni sikllar mavjud dasturda doimiy qoldirish — xotira sizishi.
Eng yaxshi yechim ko'pincha GC ni sozlash emas, sikl yaratmaslik:
- ota havolasini
weakref.ref(ota)qilish - istisnoni lokal o'zgaruvchida saqlamaslik (yoki
del xato/finally) self.ishlovchi = self.metodo'rnigaWeakMethodyoki funksiya- ish tugagach aniq
clear()/close()
3. Tez ma'lumotnoma
Sikl manbalari
ota ↔ bola oldingi ↔ keyingi obj.men = obj
xato = e (freym ↔ traceback) self.f = self.metod rekursiv ichki funksiyaAlgoritm
gc_refs = refcnt − ichki havolalar
gc_refs > 0 → tirik (va undan erishiladiganlar)
qolganlar → weakref tozalash → __del__ (bir marta) → havolalarni uzishAvlodlar (3.14)
threshold (2000, 10, 10) omon qolgan → keyingi avlod
gc.get_count() gc.get_stats() gc.callbacksSozlash
gc.disable(); yukla(); gc.enable(); gc.collect()
gc.freeze() # init / fork dan oldin
gc.set_threshold(50_000, 20, 20) # ajratish ko'p, sikl kamOldini olish
weakref.ref(ota) del xato / finally WeakMethod clear()/close()4. Batafsil misollar
Misol 1 — Havolalar hisobi yetmaydigan sikllar
"""O'ziga havola, ota-bola, ikki tomonlama ro'yxat, istisno-freym va bog'langan metod sikllari; gc.collect topadi."""
import gc
import weakref
gc.collect()
gc.disable() # avtomatik yig'ish natijani aralashtirmasin
class Tugun:
def __init__(self, nom):
self.nom = nom
def tekshir(tavsif, yaratuvchi):
havolalar = yaratuvchi()
tirik_oldin = sum(h() is not None for h in havolalar)
topildi = gc.collect()
tirik_keyin = sum(h() is not None for h in havolalar)
print(f" {tavsif:34} del dan keyin tirik: {tirik_oldin}/{len(havolalar)}, "
f"gc.collect() → {topildi:>2} ta, keyin tirik: {tirik_keyin}")
def siklsiz():
a, b = Tugun("a"), Tugun("b")
a.b = b
return [weakref.ref(a), weakref.ref(b)]
def ozi_havola():
a = Tugun("a")
a.men = a
return [weakref.ref(a)]
def ota_bola():
ota = Tugun("ota")
ota.bolalar = [Tugun(f"bola{i}") for i in range(3)]
for b in ota.bolalar:
b.ota = ota
return [weakref.ref(ota)] + [weakref.ref(b) for b in ota.bolalar]
def ikki_tomonlama():
tugunlar = [Tugun(i) for i in range(5)]
for chap, ong in zip(tugunlar, tugunlar[1:]):
chap.keyingi, ong.oldingi = ong, chap
return [weakref.ref(t) for t in tugunlar]
def istisno_freym():
katta = Tugun("katta ma'lumot")
try:
raise ValueError("xato")
except ValueError as e:
xato = e # freym → xato → traceback → freym
return [weakref.ref(katta)]
def istisno_freym_tozalangan():
katta = Tugun("katta ma'lumot")
try:
raise ValueError("xato")
except ValueError as e:
xato = e
del xato # sikl uzildi
return [weakref.ref(katta)]
class Tugma:
def __init__(self):
self.bosilganda = self.bos # self → metod → __self__ → self
def bos(self):
return "bosildi"
def bogl_metod():
return [weakref.ref(Tugma())]
print("=== Qaysi tuzilmalar sikl hosil qiladi ===")
for tavsif, f in [("sikl yo'q (a → b)", siklsiz), ("o'ziga havola", ozi_havola),
("ota ↔ 3 bola", ota_bola), ("ikki tomonlama ro'yxat (5)", ikki_tomonlama),
("⚠️ istisno lokal o'zgaruvchida", istisno_freym),
("✅ del xato bilan", istisno_freym_tozalangan),
("⚠️ self.f = self.metod", bogl_metod)]:
tekshir(tavsif, f)
print("\n=== Sikl ichidagi __del__ (PEP 442) ===")
class Resurs:
def __init__(self, nom):
self.nom = nom
def __del__(self):
print(f" __del__ {self.nom}")
x, y = Resurs("x"), Resurs("y")
x.y, y.x = y, x
del x, y
print(" del dan keyin — hech narsa chaqirilmadi")
print(f" gc.collect() → {gc.collect()}, gc.garbage: {gc.garbage}")
gc.enable()Natijaning muhim qismi:
=== Qaysi tuzilmalar sikl hosil qiladi ===
sikl yo'q (a → b) del dan keyin tirik: 0/2, gc.collect() → 0 ta, keyin tirik: 0
o'ziga havola del dan keyin tirik: 1/1, gc.collect() → 1 ta, keyin tirik: 0
ota ↔ 3 bola del dan keyin tirik: 4/4, gc.collect() → 5 ta, keyin tirik: 0
ikki tomonlama ro'yxat (5) del dan keyin tirik: 5/5, gc.collect() → 5 ta, keyin tirik: 0
⚠️ istisno lokal o'zgaruvchida del dan keyin tirik: 1/1, gc.collect() → 4 ta, keyin tirik: 0
✅ del xato bilan del dan keyin tirik: 0/1, gc.collect() → 0 ta, keyin tirik: 0
⚠️ self.f = self.metod del dan keyin tirik: 1/1, gc.collect() → 2 ta, keyin tirik: 0
=== Sikl ichidagi __del__ (PEP 442) ===
del dan keyin — hech narsa chaqirilmadi
__del__ x
__del__ y
gc.collect() → 2, gc.garbage: []Nima ko'rsatdi: 2.1, 2.5-bo'limlar. "Topildi" soni sikldagi barcha kuzatiladigan obyektlarni — nusxalar, ro'yxatlar, freym va traceback'ni (3.14 da nusxa atributlari alohida __dict__ obyektisiz saqlanadi) — o'z ichiga oladi.
Misol 2 — Algoritmni qo'lda bajarish
"""gc_refs = refcnt − ichki havolalar: kichik graf uchun erishib bo'lmaydigan obyektlarni topish va gc bilan solishtirish."""
import ctypes
import gc
import weakref
gc.disable()
class Obyekt:
def __init__(self, nom):
self.nom = nom
self.havolalar = []
def __repr__(self):
return self.nom
def refcnt(o):
return ctypes.c_ssize_t.from_address(id(o)).value
A, B, C, D, E = (Obyekt(n) for n in "ABCDE")
A.havolalar.append(B)
B.havolalar.append(A)
C.havolalar.append(D)
D.havolalar.append(C)
D.havolalar.append(E)
tashqi = [A]
guruh = [A, B, C, D, E]
nomlar = {id(o): o.nom for o in guruh}
zaif = {o.nom: weakref.ref(o) for o in guruh}
def ichki_obyektlar(o):
"""Obyekt → __dict__ → havolalar ro'yxati → obyektlar (bir qadamda ochib beramiz)."""
return list(o.havolalar)
print("=== 1. Graf ===")
for o in guruh:
print(f" {o.nom} → {ichki_obyektlar(o)}")
print(" tashqaridan: tashqi ro'yxat → A")
del A, B, C, D, E, o, guruh
def gc_refs_hisobla():
"""Hisoblashning o'zi yaratgan vaqtinchalik havolalarni nazorat obyekti bilan o'lchab, ayiramiz."""
# nazorat obyektining tashqi havolasi yo'q — unda faqat o'lchovning o'z havolalari bo'ladi
obyektlar = [z() for z in zaif.values()] + [Obyekt("nazorat")]
xom = {x.nom: refcnt(x) for x in obyektlar}
ortiqcha = xom.pop("nazorat")
natija = {nom: qiymat - ortiqcha for nom, qiymat in xom.items()}
for x in obyektlar[:-1]:
for ichki in ichki_obyektlar(x):
natija[ichki.nom] -= 1
return natija, ortiqcha
print("\n=== 2. gc_refs hisoblash ===")
gc_refs, ortiqcha = gc_refs_hisobla()
print(f" o'lchovning o'z havolalari (nazorat obyektida): {ortiqcha}")
print(f" refcnt − o'lchov − ichki havolalar: {gc_refs}")
tirik = set()
navbat = [nom for nom, qiymat in gc_refs.items() if qiymat > 0]
while navbat:
nom = navbat.pop()
if nom in tirik:
continue
tirik.add(nom)
navbat.extend(i.nom for i in ichki_obyektlar(zaif[nom]()))
erishib_bolmaydi = sorted(set(gc_refs) - tirik)
print(f" tashqaridan havolasi bor: {sorted(n for n, q in gc_refs.items() if q > 0)}")
print(f" tirik (erishiladigan): {sorted(tirik)}")
print(f" bizning bashorat — o'chiriladi: {erishib_bolmaydi}")
print("\n=== 3. Haqiqiy gc bilan solishtirish ===")
gc.collect()
haqiqiy = sorted(n for n, z in zaif.items() if z() is None)
print(f" gc o'chirdi: {haqiqiy}, bashorat bilan bir xil: {haqiqiy == erishib_bolmaydi}")
del tashqi
gc.collect()
print(f" tashqi havola olib tashlangach: o'lganlar {sorted(n for n, z in zaif.items() if z() is None)}")
gc.enable()Natijaning muhim qismi:
=== 1. Graf ===
A → [B]
B → [A]
C → [D]
D → [C, E]
E → []
tashqaridan: tashqi ro'yxat → A
=== 2. gc_refs hisoblash ===
o'lchovning o'z havolalari (nazorat obyektida): 2
refcnt − o'lchov − ichki havolalar: {'A': 1, 'B': 0, 'C': 0, 'D': 0, 'E': 0}
tashqaridan havolasi bor: ['A']
tirik (erishiladigan): ['A', 'B']
bizning bashorat — o'chiriladi: ['C', 'D', 'E']
=== 3. Haqiqiy gc bilan solishtirish ===
gc o'chirdi: ['C', 'D', 'E'], bashorat bilan bir xil: True
tashqi havola olib tashlangach: o'lganlar ['A', 'B', 'C', 'D', 'E']Nima ko'rsatdi: 2.2-bo'lim. E sikl a'zosi emas, lekin faqat D orqali ushlangani uchun u ham C–D sikli bilan birga o'ldi; A–B sikli esa tashqi havola tufayli tirik qoldi.
Misol 3 — Avlodlar, chegaralar va freeze
"""gc.get_count o'zgarishi, avtomatik yig'ishlarni callbacks bilan sanash, chegarani oshirish, disable va freeze ta'siri."""
import gc
import time
import weakref
class Tugun:
pass
print("=== 1. Hisoblagichlar ===")
gc.collect()
print(f" chegaralar: {gc.get_threshold()}, collect dan keyin: {gc.get_count()}")
vaqtincha = [[] for _ in range(1500)]
print(f" 1500 ro'yxat yaratildi: {gc.get_count()}")
vaqtincha2 = [[] for _ in range(1000)]
print(f" yana 1000 ta (chegara 2000 dan oshdi): {gc.get_count()} — 0-avlod yig'ildi, 1-avlodga +1")
del vaqtincha, vaqtincha2
print("\n=== 2. Avtomatik yig'ishlarni sanash ===")
def sana(chegara, soni=100_000):
gc.collect()
eski = gc.get_threshold()
gc.set_threshold(*chegara)
avlodlar = [0, 0, 0]
def kuzat(bosqich, malumot):
if bosqich == "start":
avlodlar[malumot["generation"]] += 1
gc.callbacks.append(kuzat)
saqlanadi = [[i] for i in range(soni)]
gc.callbacks.remove(kuzat)
gc.set_threshold(*eski)
return avlodlar
for chegara in [(2000, 10, 10), (50_000, 20, 20)]:
print(f" chegara {chegara}: 100 000 ro'yxat davomida yig'ishlar (0, 1, 2-avlod) = {sana(chegara)}")
print("\n=== 3. ⚠️ gc.disable() va sikllar ===")
gc.collect()
gc.disable()
havolalar = []
for i in range(1000):
t = Tugun()
t.men = t
havolalar.append(weakref.ref(t))
del t
print(f" 1000 ta o'ziga havolali obyekt, GC o'chiq: tirik {sum(h() is not None for h in havolalar)}")
gc.enable()
gc.collect()
print(f" gc.enable() + collect: tirik {sum(h() is not None for h in havolalar)}")
print("\n=== 4. freeze: uzoq yashaydigan obyektlarni tekshiruvdan chiqarish ===")
katta_kesh = [{"id": i, "teglar": [i]} for i in range(300_000)]
def olchov():
bosh = time.perf_counter()
gc.collect()
return time.perf_counter() - bosh
oddiy = min(olchov() for _ in range(3))
gc.freeze()
muzlatilgan = min(olchov() for _ in range(3))
print(f" muzlatilgan obyektlar: {gc.get_freeze_count() > 300_000}")
print(f" freeze dan keyin to'liq yig'ish kamida 5 marta tezroq: {oddiy > 5 * muzlatilgan}")
gc.unfreeze()
del katta_kesh
gc.collect()Natijaning muhim qismi:
=== 1. Hisoblagichlar ===
chegaralar: (2000, 10, 10), collect dan keyin: (4, 0, 0)
1500 ro'yxat yaratildi: (1506, 0, 0)
yana 1000 ta (chegara 2000 dan oshdi): (506, 1, 0) — 0-avlod yig'ildi, 1-avlodga +1
=== 2. Avtomatik yig'ishlarni sanash ===
chegara (2000, 10, 10): 100 000 ro'yxat davomida yig'ishlar (0, 1, 2-avlod) = [45, 4, 0]
chegara (50000, 20, 20): 100 000 ro'yxat davomida yig'ishlar (0, 1, 2-avlod) = [2, 0, 0]
=== 3. ⚠️ gc.disable() va sikllar ===
1000 ta o'ziga havolali obyekt, GC o'chiq: tirik 1000
gc.enable() + collect: tirik 0
=== 4. freeze: uzoq yashaydigan obyektlarni tekshiruvdan chiqarish ===
muzlatilgan obyektlar: True
freeze dan keyin to'liq yig'ish kamida 5 marta tezroq: TrueNima ko'rsatdi: 2.3, 2.4, 2.6-bo'limlar. Chegara oshirilganda 0-avlod yig'ishlari keskin kamaydi; freeze qilingan katta kesh to'liq yig'ishda qayta tekshirilmadi.
Misol 4 — Amaliy: sikllarsiz daraxt
HTML-ga o'xshash hujjat daraxti: har tugun bolalarini va otasini biladi. Ikki versiya — oddiy ota havolasi va weakref — GC o'chiq holatda sahifalarni qayta ishlaganda xotirada qancha tugun qolishini solishtiramiz.
"""Ota havolasi: kuchli (sikl) va zaif (weakref.ref); GC o'chiq holatda sizish; property orqali qulay API; tozalash."""
import gc
import weakref
class KuchliTugun:
tiriklar = weakref.WeakSet()
def __init__(self, teg, ota=None):
self.teg = teg
self.ota = ota
self.bolalar = []
if ota is not None:
ota.bolalar.append(self)
KuchliTugun.tiriklar.add(self)
def yol(self):
qismlar, t = [], self
while t is not None:
qismlar.append(t.teg)
t = t.ota
return "/".join(reversed(qismlar))
class ZaifTugun:
tiriklar = weakref.WeakSet()
def __init__(self, teg, ota=None):
self.teg = teg
self._ota = weakref.ref(ota) if ota is not None else None
self.bolalar = []
if ota is not None:
ota.bolalar.append(self)
ZaifTugun.tiriklar.add(self)
@property
def ota(self):
return self._ota() if self._ota is not None else None
def yol(self):
qismlar, t = [], self
while t is not None:
qismlar.append(t.teg)
t = t.ota
return "/".join(reversed(qismlar))
def sahifa_qur(klass, raqam):
html = klass("html")
body = klass("body", html)
for i in range(20):
div = klass(f"div{i}", body)
klass("p", div)
klass("a", div)
return html.bolalar[0].bolalar[raqam % 20].bolalar[1].yol()
print("=== 1. Bir xil API ===")
for klass in (KuchliTugun, ZaifTugun):
print(f" {klass.__name__}: {sahifa_qur(klass, 7)}")
print("\n=== 2. GC o'chiq holatda 100 ta sahifa ===")
gc.collect()
gc.disable()
for klass in (KuchliTugun, ZaifTugun):
for i in range(100):
sahifa_qur(klass, i)
print(f" {klass.__name__:12} xotirada qolgan tugunlar: {len(klass.tiriklar):>5} (bir sahifa — 62 tugun)")
gc.enable()
topildi = gc.collect()
print(f" gc.collect(): {topildi} ta obyekt yig'ildi → KuchliTugun qoldi: {len(KuchliTugun.tiriklar)}")
print("\n=== 3. ⚠️ Zaif ota — daraxt ildizini ushlab turish kerak ===")
bola = ZaifTugun("span", ZaifTugun("div"))
print(f" ota vaqtinchalik edi: bola.ota = {bola.ota}, yol = {bola.yol()!r}")
div = ZaifTugun("div")
bola2 = ZaifTugun("span", div)
print(f" ota nomda saqlandi: yol = {bola2.yol()!r}")
print("\n=== 4. Kuzatuv: yig'ishlarni callbacks bilan jurnallash ===")
jurnal = []
def kuzat(bosqich, malumot):
if bosqich == "stop" and malumot["collected"]:
jurnal.append((malumot["generation"], malumot["collected"]))
gc.collect()
gc.callbacks.append(kuzat)
for i in range(200):
sahifa_qur(KuchliTugun, i)
gc.collect()
gc.callbacks.remove(kuzat)
yigilgan = sum(s for _, s in jurnal)
print(f" 200 kuchli sahifa: {len(jurnal)} ta yig'ish obyekt topdi, jami {yigilgan} ta obyekt")
print(f" avlodlar bo'yicha: { {a: sum(s for g, s in jurnal if g == a) for a in (0, 1, 2)} }")Natijaning muhim qismi:
=== 1. Bir xil API ===
KuchliTugun: html/body/div7/a
ZaifTugun: html/body/div7/a
=== 2. GC o'chiq holatda 100 ta sahifa ===
KuchliTugun xotirada qolgan tugunlar: 6200 (bir sahifa — 62 tugun)
ZaifTugun xotirada qolgan tugunlar: 0 (bir sahifa — 62 tugun)
gc.collect(): 18600 ta obyekt yig'ildi → KuchliTugun qoldi: 0
=== 3. ⚠️ Zaif ota — daraxt ildizini ushlab turish kerak ===
ota vaqtinchalik edi: bola.ota = None, yol = 'span'
ota nomda saqlandi: yol = 'div/span'
=== 4. Kuzatuv: yig'ishlarni callbacks bilan jurnallash ===
200 kuchli sahifa: 18 ta yig'ish obyekt topdi, jami 37200 ta obyekt
avlodlar bo'yicha: {0: 30318, 1: 3906, 2: 2976}Nima ko'rsatdi: 2.1, 2.6-bo'limlar — ota havolasini zaif qilish sikllarni butunlay yo'q qildi: tugunlar GC o'chiq bo'lsa ham darhol o'ldi. Zaif ota bilan esa ildizni biror nom ushlab turishi kerak.
5. To'g'ri va noto'g'ri tushunishlar
| Noto'g'ri fikr | To'g'risi |
|---|---|
| "Python xotirasini faqat GC boshqaradi" | Ko'p obyekt havolalar hisobi bilan darhol o'ladi; GC — faqat sikllar uchun |
"gc.disable() barcha tozalashni to'xtatadi" |
Faqat sikl yig'uvchini; havolalar hisobi ishlayveradi |
| "GC hamma obyektlarni tekshiradi" | Faqat kuzatiladigan konteynerlarni, avlodlar bo'yicha |
"__del__ li sikllar yig'ilmaydi va gc.garbage ga tushadi" |
3.4 dan yig'iladi (PEP 442) |
"Sikl bo'lsa, del bilan obyektlar o'ladi" |
Faqat keyingi GC yig'ishida |
| "Istisnoni lokal o'zgaruvchida saqlash zararsiz" | Freym ↔ traceback sikli |
| "GC pauzalari kichik, ahamiyatsiz" | Millionlab uzoq yashaydigan obyektda to'liq yig'ish sezilarli |
"gc.collect() ni tez-tez chaqirish xotirani tejaydi" |
Sikllar yo'q bo'lsa — faqat CPU sarfi |
6. Keng tarqalgan xatolar va yechimlari
1. Ota-bola daraxtida kuchli ota havolasi
bola.ota = ota # ❌ har tugun sikl — faqat GC tozalaydi
bola._ota = weakref.ref(ota) # ✅ property orqali ota() ni qaytaring2. Istisnoni lokal o'zgaruvchida saqlash
except ValueError as e:
oxirgi_xato = e # ❌ freym ↔ traceback sikli
...
except ValueError as e:
oxirgi_xato = str(e) # ✅ yoki funksiya oxirida del oxirgi_xato3. self.callback = self.metod
self.ishlovchi = self.qayta_ishla # ❌ sikl
def ishlovchi(self, *a): # ✅ oddiy metod chaqiring yoki WeakMethod
return self.qayta_ishla(*a)4. Sikllar bor dasturda doimiy gc.disable()
gc.disable() # ❌ "tezlashtirish" — sekin xotira sizishi O'chirishni faqat yuklash oynasida qiling, keyin gc.enable() va gc.collect().
5. Har so'rovdan keyin gc.collect()
def javob_ber(sorov):
...
gc.collect() # ❌ to'liq yig'ish — har so'rovga o'nlab msGC ni o'z holiga qo'ying yoki chegaralarni sozlang; sikllarni manbada yo'q qiling.
6. Katta keshni yuklagach muzlatmaslik
model = yukla_katta_model() # millionlab obyekt
# ❌ har to'liq yig'ish ularni qayta tekshiradi
gc.freeze() # ✅ yuklangandan keyin7. Sikldagi __del__ da boshqa a'zoga murojaat
def __del__(self):
self.sherik.yop() # ❌ sherik allaqachon tozalangan bo'lishi mumkin Resurslarni with yoki weakref.finalize bilan yoping.
8. Sizishni gc.garbage bilan qidirish
print(gc.garbage) # ❌ zamonaviy Python'da deyarli har doim bo'sh
gc.set_debug(gc.DEBUG_SAVEALL); gc.collect() # ✅ yig'ilganlarni ko'rish (faqat tuzatishda)7. Integratsiya — bu bilim qayerda kerak bo'ladi
- 9.5-dars (o'tilgan):
except ... as eblok oxirida o'chirilishi — sikllarni oldini olish uchun - 10.12-dars (o'tilgan): o'zini chaqiruvchi ichki funksiya sikli
- 11.4-dars (o'tilgan): havolalar hisobi — GC uning to'ldiruvchisi
- 11.10-dars:
weakref— sikllarni uzishning asosiy vositasi - 12-qism: GC ob'ektlari C darajasida,
tp_traversevatp_clear - 14-qism: GC va iplar; erkin ipli yig'mada GC
- 28-qism: gunicorn preload,
forkvagc.freeze() - 29-qism: xotira optimizatsiyasi va kechikish pauzalarini o'lchash
8. Eng yaxshi amaliyotlar
Sikllar yaratmang, ularni GC ga qoldirmang — ota havolalari zaif, istisnolar matn sifatida.
GC ni o'chirmang — faqat qisqa, aniq oynalarda, keyin albatta
enablevacollect.Uzoq yashaydigan katta tuzilmalarni yuklagach —
gc.freeze().forkqiluvchi serverlarda ishga tushishdan keyingc.freeze().Chegaralarni o'lchab o'zgartiring —
gc.callbacksbilan yig'ishlar soni va davomiyligini yozib.Sikldagi
__del__ga tayanmang.Xotira o'sishini
weakref+gc.collect()testlari bilan tekshiring — "del dan keyin tirikmi?".gc.collect()ni qo'lda faqat tuzatish, testlar va boshqariladigan oynalarda chaqiring.
9. Amaliy topshiriq
Vazifa 1: Natijani bashorat qiling
import gc, weakref
class T:
pass
gc.collect()
1. a = T()
a.me = a
w = weakref.ref(a)
del a
print(w() is None)
2. gc.collect()
print(w() is None)
3. b = T()
c = T()
b.c = c
w2 = weakref.ref(b)
del b, c
print(w2() is None)
4. print(gc.is_tracked(1), gc.is_tracked([]), gc.is_tracked("s"))
5. print(gc.is_tracked(0.5), gc.is_tracked(T()))
6. print(gc.get_threshold()[0])
7. gc.disable()
x = T()
x.x = x
wx = weakref.ref(x)
del x
print(gc.isenabled(), wx() is None)
8. print(gc.collect() >= 1, wx() is None)
gc.enable()
9. class Q:
def __init__(self, ota):
self.ota = weakref.ref(ota)
class P:
def __init__(self):
self.bola = Q(self)
p = P()
wp = weakref.ref(p)
del p
print(wp() is None)
10. print(gc.garbage)
11. gc.freeze()
print(gc.get_freeze_count() > 0)
gc.unfreeze()
12. def f():
t = T()
try:
1 / 0
except ZeroDivisionError as e:
xato = e
return weakref.ref(t)
wt = f()
print(wt() is None, end=" ")
gc.collect()
print(wt() is None)Javoblar
False— o'ziga havola sikliTrue— GC siklni topdiTrue— sikl yo'q, havolalar hisobi darhol o'chirdiFalse True FalseFalse True—floatkonteyner emas, klass nusxasi esa kuzatiladi2000False FalseTrue True— qo'lda yig'ish GC o'chiq bo'lsa ham ishlaydiTrue— bola otani zaif havola bilan ushlaydi, sikl yo'q[]TrueFalse True—xatolokal o'zgaruvchisi freym ↔ traceback siklini yaratdi
Vazifa 2: Xatolarni tuzating
1. class Menyu:
def __init__(self, nom, ota=None):
self.nom, self.ota, self.bolalar = nom, ota, []
if ota:
ota.bolalar.append(self)
2. def yuklash():
gc.disable()
return [yukla(f) for f in fayllar]
3. def qayta_urin(f):
oxirgi = None
for _ in range(3):
try:
return f()
except IOError as e:
oxirgi = e
raise oxirgi
4. class Kuzatuvchi:
def __init__(self, manba):
self.manba = manba
manba.obunachilar.append(self.yangila)
5. @app.after_request
def tozala(javob):
gc.collect()
return javobJavoblar
1. class Menyu:
def __init__(self, nom, ota=None):
self.nom, self.bolalar = nom, []
self._ota = weakref.ref(ota) if ota else None
if ota:
ota.bolalar.append(self)
@property
def ota(self):
return self._ota() if self._ota else None
2. def yuklash():
gc.disable()
try:
return [yukla(f) for f in fayllar]
finally:
gc.enable() # xato bo'lsa ham qayta yoqiladi
3. def qayta_urin(f):
oxirgi = None
for _ in range(3):
try:
return f()
except IOError as e:
oxirgi = e
try:
raise oxirgi
finally:
oxirgi = None # freym ↔ traceback siklini uzish
4. class Kuzatuvchi:
def __init__(self, manba):
self.manba = manba
manba.obunachilar.append(weakref.WeakMethod(self.yangila))
# manba ↔ kuzatuvchi sikli va kuzatuvchini abadiy ushlash yo'qoladi
5. # Har so'rovdan keyin to'liq yig'ish — kechikish manbai. Olib tashlang;
# kerak bo'lsa server ishga tushgach gc.freeze() va sikllarni manbada yo'q qilish.Vazifa 3: Sikl detektori
sikllarni_top(ildiz) funksiyasini yozing:
gc.get_referentsbo'ylab chuqurlik bo'yicha qidirib, ildizdan erishiladigan barcha sikllarni topsin (Tarjan algoritmi yoki DFS + stek)- Har siklni tur nomlari zanjiri sifatida chiqarsin:
Tugun → dict → list → Tugun __dict__va hujayralar kabi oraliq obyektlarni "qisqartirish" rejimi- Misol 1 dagi tuzilmalarga qo'llang
Vazifa 4: GC profilchisi
Kontekst menejer yozing: with gc_profil() as p:
gc.callbacksorqali har yig'ishning avlodi, davomiyligi (time.perf_counter), yig'ilgan va yig'ilmaydigan obyektlar sonini yozsin- Blok tugagach hisobot: avlod bo'yicha soni, jami va eng uzun pauza
- Blok ichida xato bo'lsa ham callback olib tashlansin
- Misol 3 dagi turli chegaralar bilan solishtiring
Vazifa 5: Zaif ota daraxt kutubxonasi
Daraxt va Tugun klasslarini yozing:
- Tugunlar ota havolasini zaif saqlasin, daraxt esa ildizni kuchli ushlasin
tugun.ajrat()— daraxtdan ajratish,daraxt.tugunlar()— generatorcopy.deepcopy(daraxt)vapicklezaif havolalar bilan to'g'ri ishlasin (__getstate__/__setstate__)- Test: GC o'chiq holatda 10 000 daraxt yaratib o'chirganda tirik tugunlar soni 0 bo'lsin
Vazifa 6: fork va copy-on-write tajribasi (Linux/macOS)
- Ota jarayonda 500 MB lik ro'yxatlar tuzilmasini yarating
os.fork()bilan 4 ta bola jarayon oching, har birigc.collect()chaqirsin- Har bola jarayonning xususiy xotirasini (
/proc/self/smaps_rollupdagiPrivate_Dirty) o'lchang - Xuddi shuni ota jarayonda
gc.freeze()qilib takrorlang va farqni tushuntiring
Vazifa 7: O'ylash
Instagram muhandislari 2017 yilda o'z Django serverlarida Python GC ni o'chirib, xotira sarfini kamaytirganliklari haqida yozgan; keyinchalik Python 3.7 da gc.freeze() qo'shildi. Nega GC ni o'chirish xotirani kamaytirishi mumkin, qanday xavflari bor va gc.freeze() bu muammoni qanday nozikroq hal qiladi?
Javob
Qisqa javob: fork qiluvchi serverlarda bola jarayonlar ota jarayon xotirasini copy-on-write asosida bo'lishadi: sahifa faqat unga yozilganda nusxalanadi. Sikl yig'uvchi esa obyektlarni tekshirganda ularning sarlavhasiga (GC ma'lumotlari va havolalar soni) yozadi — natijada bo'lishilgan sahifalar birin-ketin nusxalanib, har bola jarayon xotirasi o'sadi. GC ni o'chirish bu yozuvlarni to'xtatadi, lekin sikllar tozalanmay qoladi. gc.freeze() esa ishga tushishdagi obyektlarni GC dan "yashiradi" — ular tekshirilmaydi va sahifalari yozilmaydi — yangi sikllar esa odatdagidek yig'iladi.
1. Copy-on-write va Python
- Gunicorn kabi serverlar ilovani ota jarayonda yuklab, keyin
forkbilan worker'lar yaratadi - Operatsion tizim xotira sahifalarini bo'lishadi: worker faqat sahifaga yozganda uning nusxasi yaratiladi
- Python'da o'qish ham yozadi: havolalar sonini oshirish/kamaytirish — sarlavhaga yozuv. Shuning uchun bo'lishish tabiatan "sizib" boradi
- Sikl yig'uvchi esa bundan ham ko'proq yozadi: har kuzatiladigan obyektning GC maydonlari yig'ish paytida o'zgartiriladi — ishlatilmayotgan obyektlar ham
2. Instagram tajribasi
Instagram muhandislari o'z blogida ("Dismissing Python Garbage Collection at Instagram") tasvirlaganidek, GC ni o'chirgach worker'lar orasidagi bo'lishilgan xotira ulushi oshgan va umumiy xotira sarfi kamaygan. Ular GC ni butunlay o'chirishning xavfini ham ta'kidlagan: sikllar tozalanmaydi, shuning uchun xotira nazorati va worker'larni davriy qayta ishga tushirish kerak bo'lgan.
3. GC ni o'chirish xavflari
| Xavf | Oqibat |
|---|---|
| Sikllar tozalanmaydi | Sekin xotira sizishi — worker'larni qayta ishga tushirishga majbur |
| Uchinchi tomon kutubxonalar sikl yaratadi | Nazorat qilib bo'lmaydigan sizish |
gc.enable() qayta chaqirilishi |
Kutubxona yoki test kodi GC ni qayta yoqib yuborishi mumkin |
| Tuzatish qiyinligi | "Nega xotira o'syapti?" savoliga javob topish murakkablashadi |
4. gc.freeze() qanday yordam beradi
gc.freeze()— hozir kuzatilayotgan barcha obyektlarni "doimiy avlodga" o'tkazadi- Keyingi yig'ishlar ularni tekshirmaydi → ularning sahifalariga GC yozuvi yo'q
- Keyin yaratilgan obyektlar va sikllar odatdagidek yig'iladi
- Python hujjatlari tavsiyasi: ota jarayonda
forkdan oldingc.disable(), ilovani yuklab,gc.freeze(), bola jarayonda esagc.enable()— ota jarayondaforkdan oldingi tasodifiy yig'ish sahifalarni "iflos" qilmasligi uchun
freeze havolalar sonini yangilashni to'xtatmaydi — o'qish ham baribir ba'zi sahifalarni nusxalaydi. Python 3.12 dagi o'lmas obyektlar (PEP 683) bu muammoning bir qismini — ko'p ishlatiladigan umumiy obyektlar uchun — hal qildi.
5. Umumiy saboq
- Xotira muammosini o'lchang:
Private_Dirty,Shared— "Python xotirani ko'p yeydi" degan taxmin emas - GC ni o'chirishdan oldin sikllarni manbada yo'q qiling
forkmodelida: yuklash →gc.freeze()→fork- Uzoq yashaydigan katta tuzilmalar uchun ham
freezepauzalarni kamaytiradi - O'lmas obyektlar va erkin ipli yig'ma (14-qism) CPython xotira modelini shu yo'nalishda rivojlantirmoqda
6. Xulosa
forkqiluvchi serverlarda sahifalar copy-on-write bilan bo'lishiladi, Python esa sarlavhalarga yozib bu bo'lishishni buzadi- Sikl yig'uvchi ishlatilmayotgan obyektlarga ham yozadi — GC ni o'chirish bo'lishilgan xotirani saqlaydi
- Narxi — sikllar tozalanmaydi va xotira sekin sizadi
gc.freeze()ishga tushishdagi obyektlarni GC dan chiqarib, yangi sikllarni esa yig'ishda davom etadi- Eng yaxshi natija: sikllarni kamaytirish + yuklashdan keyin
freeze+ o'lchov
Nimani mustahkamlaydi: 2.1–2.6-bo'limlar.
Xulosa
Bu darsda havolalar hisobini to'ldiruvchi sikl yig'uvchini o'rgandik.
Eng muhim uch fikr:
Sikllar havolalar hisobini aldaydi. Ota ↔ bola, ikki tomonlama ro'yxat va o'ziga havola kabi ko'rinadigan sikllar hamda lokal o'zgaruvchidagi istisno (freym ↔ traceback), atributdagi bog'langan metod va rekursiv ichki funksiya kabi yashirin sikllar
deldan keyin ham tirik qoladi. Ularni faqat sikl yig'uvchi topadi —__del__li sikllar ham (PEP 442),gc.garbageesa odatda bo'sh.GC ichki havolalarni ayirib, tashqaridan erishib bo'lmaydigan guruhlarni topadi. Faqat kuzatiladigan konteynerlar tekshiriladi, uch avlod bo'yicha: Python 3.14 da 0-avlod 2000 ta ajratishdan keyin, keyingilari esa 10 martadan keyin yig'iladi. Avtomatik yig'ish ixtiyoriy joyda boshlanadi va katta xotirada to'liq yig'ish sezilarli pauza beradi —
gc.callbacksvagc.get_statsbilan o'lchang.Eng yaxshi sozlash — sikl yaratmaslik. Ota havolasini
weakref.refqiling, istisnolarni matn sifatida saqlang yokidelqiling, bog'langan metodni atributga qo'ymang. GC ni faqat qisqa yuklash oynalarida o'chiring, uzoq yashaydigan katta tuzilmalar vaforkqiluvchi serverlar uchun esagc.freeze()dan foydalaning.
Keyingi darsda eng mashhur Python tuzoqlaridan biriga qaytamiz: o'zgaruvchan standart argument — endi obyekt modeli nuqtai nazaridan: sukut qiymat qachon yaratiladi, qayerda saqlanadi, va bu "xato" qachon ataylab foydali.
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!