Ders 07 / 10
Profil Çıkarma
Belirlenimci profil aracı çağrı sayısını verir, süre sütunlarını değil; aynı 80 adımlık iş iki yükte de 80 kez ilerletilir ama sıcak yoldaki işlev girdi/çıktı ağırlıklı yükte 136, işlemci ağırlıklı yükte 322 kez çağrılır.
İçindekiler
Önceki konu altı derste dört rejimi yan yana koydu ve her birinin hangi yükte ne kazandırdığını saydı. Son ders yürütücü havuzlarını ölçtükten sonra şu soruyu bıraktı: buraya kadar hangi rejimin ne kazandırdığı sayıldı — peki bir programda hangi adımın ağır olduğunu nereden bilirim?
Soru boş bir soru değil. Rejim seçmek, ağır adımın hangi türden olduğunu — işlemci adımı mı, girdi/çıktı adımı mı — bildiğini varsayar. Varsayım yanlışsa seçim de yanlış olur: işlemci ağırlıklı bir yüke işçi eklemek 80 tiki ancak 73’e indirir. Bu dersin yanıtı şudur: bilinmez, ölçülür. Ve bu kursta ölçülen şey süre değil, çağrı sayısıdır (call count).
Belirlenimci Sayaç
Profil çıkarma (profiling), bir koşumun hangi parçada ne kadar iş yaptığını kayda geçirme işidir. İki aile araç vardır. Örnekleyen araç koşumu düzenli aralıklarla yoklar ve nerede olduğunu not eder; topladığı şey bir örneklemdir ve iki koşumda aynı çıkmaz. Belirlenimci araç ise her çağrıyı ve her dönüşü görür; kaçırdığı olay yoktur.
Standart kitaplığın belirlenimci profil aracı iki tür veri toplar: bir işlevin kaç kez çağrıldığı ve o çağrılarda ne kadar zaman geçtiği. Bu dersin tablosunda ikincisi yoktur, ve nedeni tek cümleyle şudur: aynı kod aynı makinede iki kez koştuğunda süre sütunları farklı çıkar, çağrı sayısı ise değişmez — karşılaştırılabilir olan sayıdır.
Bunun bir sonucu var: aracın hazır dökümünü olduğu gibi basmıyoruz, çünkü o döküm süre sütunlarını da taşır. Onun yerine toplanan kayıt açılıyor ve her satırdan yalnız çağrı sayısı okunuyor. Bu bir kısıtlama gibi görünür; dersin sonunda görüleceği gibi, elde kalan sayı sorunun tamamını yanıtlamaya yetiyor.
Kaydın biçimi bunu doğrudan destekler. Araç, koşum bittiğinde her işlev için bir giriş tutar; girişin anahtarı işlevi tanımlayan üçlüdür — dosya, satır ve ad — değeri ise o işlevle ilgili sayıların demeti. Demetin ilk iki alanı çağrı sayılarıdır: özyinelemesiz çağrı sayısı ve toplam çağrı sayısı. Özyineleme olmayan bir programda ikisi eşittir. Kalan alanlar zamanla ilgilidir ve bu derste okunmaz.
Sıcak Yol
Sıcak yol (hot path), bir koşumda en sık çalıştırılan kod parçasıdır. Adı yanıltıcı olabilir: sıcaklık kodun ne yaptığıyla değil, kaç kez çalıştığıyla ilgilidir. Bir kez çağrılan ağır bir işlev sıcak yol değildir; yüzlerce kez çağrılan üç satırlık bir işlev sıcak yoldur.
Sıcak yolu sezgiyle bulmak, kodun uzun görünen kısmıyla çok çalışan kısmını karıştırmak demektir. Uzunluk göz kararı ölçülür, çağrı sayısı ölçülmez — sayılır. Bu derste sıcak yol tek bir ölçüte bağlanır: en çok çağrılan işlev.
Bu ölçüt tek başına bir eniyileme kararı vermez, ama arama alanını daraltır. Bir programın çoğu işlevi koşumda birkaç kez çağrılır; bir avuç işlev ise yüzlerce kez. İlk gruptaki bir işlevi yeniden yazmanın toplam üzerindeki etkisi, çağrı sayısı kadarıyla sınırlıdır. İkinci gruptaki bir işlevde kazanılan her adım, çağrı sayısıyla çarpılarak geri döner.
Ölçülecek Program
Ölçüm için yeni bir program yazmıyoruz; ortak tanımın çizelgeleyicisini ölçüyoruz. Bir engel
var: kosum tek bir işlevdir ve profil aracının tablosunda tek bir satır üretir. Bir satır
sıcak yolu göstermez.
Bu yüzden gövde adlandırılmış parçalara bölünür: etkinleri_sec, islemci_adimi_mi,
ilerlet ve tek bir tiki yürüten tik_at. Davranış değişmez — bölünmüş işlev, dört
rejim ile üç yükün on iki bileşiminin hepsinde özgün işlevle aynı dörtlüyü döndürür.
Değişen tek şey, aracın görebildiği sınır sayısıdır.
Buradan profil çıkarmanın ilk kuralı çıkar: araç yalnız çağrı sınırlarını görür. Bir döngü gövdesi ayrı bir işlev değilse, tabloda satırı da yoktur. Ölçümün çözünürlüğünü aracın kendisi değil, kodun bölünme biçimi belirler.
Aynı sayıları elle konan sayaçlarla da toplayabilirdik: her işlevin başına bir artırma satırı koyup sonunda basmak. İki farkı var. Elle konan sayaç nereye bakacağını önceden bilmeyi gerektirir; sıcak yolun hangisi olduğunu bilseydik zaten ölçmeye gerek kalmazdı. İkincisi, sayaç satırları kodun kendisine karışır ve ölçüm bittiğinde tek tek sökülmeleri gerekir. Araç ise kodu değiştirmeden dışarıdan takılır ve çıkarılır; ölçülen program, yazılan programın aynısı kalır.
Ölçümün varsayımları:
- BS1 — Görev kümesi ortak tanımdan gelir: sekiz görev, görev başına on adım, toplam 80 adım. Kâhin kurgunun kendisidir; toplam adım sayısını ölçmeden biliyoruz.
- BS2 — Bölünmüş
kosum, özgün işlevle aynı dörtlüyü döndürür. Bölme bir yeniden yazım değil, ölçüm için sınır açmadır;gorevlervekosumdavranışı korunur. - BS3 — Ölçülen rejim sekiz işçili, tek yuvalı rejimdir. İki yük denenir — girdi/çıktı ağırlıklı ve işlemci ağırlıklı — ve yükten başka hiçbir şey değişmez.
- BS4 — Kayıttan yalnız çağrı sayısı okunur. Süre alanları araç tarafından toplanır ama yazılmaz; yazılsalardı iki koşum arasında değişen tek sütun onlar olurdu.
- BS5 — Tabloya yalnız dersin kendi işlevleri alınır; yerleşiklerin ve standart kitaplığın çağrıları sayılmaz.
- BS6 — Aracın kendisi koşuma bir yük ekler. Bu yük çağrı sayısını değiştirmez, çünkü sayaç çağrıları saymakla yetinir, yeni çağrı üretmez.
Ölçüm
"""Profil cikarma: ayni is, iki yuk; cagri sayisi sicak yolu gosterir.""" import cProfile import pstats TOHUM = 20260817 ISLEMCI, GC = "islemci", "g_c" def uretec(tohum): d = tohum % 2147483646 + 1 def r(n): nonlocal d d = (d * 48271) % 2147483647 return d % n return r def gorevler(sayi=8, adim=10, gc_payi=7, tohum=TOHUM): r, liste = uretec(tohum), [] for i in range(sayi): liste.append([GC if r(10) < gc_payi else ISLEMCI for _ in range(adim)]) return liste def etkinleri_sec(kalan, isci): return [i for i, g in enumerate(kalan) if g][:isci] def islemci_adimi_mi(adim): return adim == ISLEMCI def ilerlet(kalan, i): kalan[i].pop(0) def tik_at(kalan, isci, islemci_yuvasi): etkin = etkinleri_sec(kalan, isci) yuva, ilerleyen, ia, ga = islemci_yuvasi, 0, 0, 0 for i in etkin: if islemci_adimi_mi(kalan[i][0]): if yuva <= 0: continue yuva -= 1 ia += 1 else: ga += 1 ilerlet(kalan, i) ilerleyen += 1 return ilerleyen, ia, ga def kosum(gorevler, isci, islemci_yuvasi): """Ortak tanimla ayni sonucu verir; govdesi adlandirilmis parcalara bolunmustur.""" kalan = [list(g) for g in gorevler] tik = ortusen = islemci_adim = gc_adim = 0 while any(kalan): ilerleyen, ia, ga = tik_at(kalan, isci, islemci_yuvasi) if not ilerleyen: break islemci_adim += ia gc_adim += ga tik += 1 ortusen += max(0, ilerleyen - 1) return tik, ortusen, islemci_adim, gc_adim IZLENEN = ("kosum", "tik_at", "etkinleri_sec", "islemci_adimi_mi", "ilerlet") def cagrilari_say(gc_payi, isci=8, yuva=1): """Yalniz cagri sayisi okunur; sure sutunlari alinmaz.""" g = gorevler(gc_payi=gc_payi) olcer = cProfile.Profile() olcer.enable() sonuc = kosum(g, isci, yuva) olcer.disable() sayim = {ad: 0 for ad in IZLENEN} for (_, _, ad), veri in pstats.Stats(olcer).stats.items(): if ad in sayim: sayim[ad] = veri[1] return sonuc, sayim gc_sonuc, gc_sayim = cagrilari_say(7) is_sonuc, is_sayim = cagrilari_say(1) print(f"{'işlev':<18s} {'girdi/çıktı ağırlıklı':>22s} {'işlemci ağırlıklı':>19s}") for ad in IZLENEN: print(f"{ad:<18s} {gc_sayim[ad]:22d} {is_sayim[ad]:19d}") print(f"{'toplam çağrı':<18s} {sum(gc_sayim.values()):22d} " f"{sum(is_sayim.values()):19d}") print() print(f"girdi/çıktı ağırlıklı: {gc_sonuc[0]} tik, {gc_sonuc[1]} örtüşen adım") print(f"işlemci ağırlıklı : {is_sonuc[0]} tik, {is_sonuc[1]} örtüşen adım") print(f"iki koşumda da ilerlet çağrısı: {gc_sayim['ilerlet']} ve " f"{is_sayim['ilerlet']}; kâhinin verdiği toplam adım " f"{sum(len(x) for x in gorevler())}") print("üç ardışık koşumda islemci_adimi_mi çağrısı:", [cagrilari_say(1)[1]["islemci_adimi_mi"] for _ in range(3)])
işlev girdi/çıktı ağırlıklı işlemci ağırlıklı kosum 1 1 tik_at 31 73 etkinleri_sec 31 73 islemci_adimi_mi 136 322 ilerlet 80 80 toplam çağrı 279 549 girdi/çıktı ağırlıklı: 31 tik, 49 örtüşen adım işlemci ağırlıklı : 73 tik, 7 örtüşen adım iki koşumda da ilerlet çağrısı: 80 ve 80; kâhinin verdiği toplam adım 80 üç ardışık koşumda islemci_adimi_mi çağrısı: [322, 322, 322]
Eşit Kalan Satır
Tablodaki en önemli satır, iki sütunu eşit olan satırdır: ilerlet her iki yükte de
80 kez çağrılıyor. Kâhin bunu doğruluyor — kurgunun toplam adım sayısı 80’dir ve her
adım tam olarak bir kez atılır. İş değişmedi.
Değişen satırlar bunun etrafında duruyor. tik_at 31’den 73’e, etkinleri_sec
aynı biçimde 31’den 73’e, islemci_adimi_mi 136’dan 322’ye çıkıyor. Toplam
çağrı 279’dan 549’a. Aynı 80 adım, iki katına yakın çağrı.
Buradan dersin ana okuması çıkıyor: çağrı sayısı yapılan işi değil, işe varma çabasını sayar. İki koşumda da 80 adım atıldı; fark, o 80 adıma ulaşmak için kaç kez soru sorulduğundadır.
Eşit kalan satırın ikinci bir işlevi daha var: ölçümü doğruluyor. ilerlet çağrısının
80 çıkması, kurgudan bağımsız olarak bilinen bir sayıyla profil tablosunun uyuşması
demektir. Uyuşmasaydı, tabloyu değil aracı ya da bölmeyi sorgulamak gerekirdi. Bilinen bir
sayıyı da ölçmek, ölçümün kendisini sınamanın en ucuz yoludur; bir profil tablosunda
kâhinden gelen böyle bir satır varsa, kalan satırlar da güvenle okunur.
Sıcak Yol Hangisi
En çok çağrılan işlev iki sütunda da islemci_adimi_mi. Sıcak yol budur ve dikkat çeken
şey sayının 80’den büyük olmasıdır: 136 ve 322. Adım başına birden çok çağrı demektir.
Nedeni çizelgeleyicinin kendisinde. Bir tikte bakılan her görev bu işlevi çağırır — ilerleyebilen de, yuva bulamadığı için geri çevrilen de. Girdi/çıktı ağırlıklı yükte adımların çoğu yuva istemiyor, görevler erken tükeniyor ve toplam bakış 136’da kalıyor. İşlemci ağırlıklı yükte tek yuva her tikte bir görev dışındakileri geri çeviriyor; geri çevrilen görev tükenmeden duruyor, sonraki tikte yeniden bakılıyor ve her bakış bir çağrıya mal oluyor. Aradaki fark 186 çağrıdır.
Bölme işlemi bu açıklamayı sınıyor. Girdi/çıktı ağırlıklı koşumda tik başına ortalama bakış 136/31, yani 4,39; işlemci ağırlıklı koşumda 322/73, yani 4,41. İki sayı neredeyse aynı. Demek ki bir tikte bakılan görev sayısı yükten pek etkilenmiyor; farkı tümüyle tik sayısı üretiyor. Sıcak yol sıcaklığını kendi içindeki bir şeyden değil, kaç tur döndüğünden alıyor.
Bu, sıcak yolla ilgili sık yapılan bir yanlışı düzeltir: en çok çağrılan işlevin kendisi
kusurlu olmak zorunda değildir. islemci_adimi_mi üç satırlık, tek karşılaştırmalı bir
işlev; içinde düzeltilecek bir şey yok. Onu 322 kez çağıran şey, dışarıdaki yuva kısıtıdır.
Sıcak yol nedeni değil, göstergesidir.
tik_at ile etkinleri_sec satırları ayrı bir şey söylüyor: değerleri tik sayısına
birebir eşit — 31 ve 73. Profil tablosu, hiç tik saymadan tik sayısını verdi. Bu bir
rastlantı değil; iki işlev de tik başına tam bir kez çağrılıyor. Kural şu: bir işlevin
çağrı sayısı, o işlevin temsil ettiği olayın sayısıdır. Ölçümün neyi sayacağını aracın
kendisi değil, kodun bölünme biçimi belirler — ve bu, tik_at adında bir işlev yazma
kararının ölçüm kararı olduğu anlamına gelir.
Son satır belirlenimciliği gösteriyor: aynı koşum üç kez yinelendiğinde islemci_adimi_mi
çağrısı 322, 322, 322. Süre sütunları alınsaydı üçü de farklı olurdu.
Gözlemcinin Etkisi
Belirlenimci aracın bedeli vardır: her çağrı sınırında sayaç devreye girer, kaydı günceller ve akışı geri verir. Profil altında koşan program, profilsiz koşan programın aynısı değildir. Bu, ölçüm araçlarının bilinen kusurudur ve süre ölçen bir tabloda doğrudan sonuca sızar — çok çağrılan küçük işlevler, sayaç yükü kendi paylarına eklendiği için olduklarından ağır görünür.
Çağrı sayısı bu kusurdan etkilenmez, ve nedeni tanımın kendisindedir: sayaç çağrıları
sayar, üretmez. islemci_adimi_mi sayaç varken de yokken de tam 322 kez çağrılır,
çünkü onu çağıran şey aracın varlığı değil, görev listesinin içeriğidir. Ölçüm aracının
gözlediği şeyi değiştirmediği ender durumlardan biridir bu — ve kursun süre yerine adım
saymasının ikinci gerekçesidir.
Buradan pratik bir sonuç çıkar: bir programın çağrı dökümü, profil altında bir kez alınıp sonraki koşumlar için de geçerli sayılabilir. Süre dökümü için aynı şey söylenemez.
Çağrı Sayısının Söylemediği
Tablonun ilk satırı bu sınırı gösteriyor: kosum yalnız 1 kez çağrıldı. Çağrı sayısına
bakan bir okuma onu en soğuk işlev sayar; oysa koşumun tamamı onun içinde geçiyor. Bir
işlevi bir kez çağırmakla ucuza getirmiş olmuyoruz.
İkinci sınır kapsama sınırıdır. IZLENEN demeti dışında kalan her şey — yerleşik çağrılar,
liste işlemleri, biçimlendirme — tabloya girmiyor. Onlar da çalıştı; sayılmadılar. Bir
profil tablosu, neyi izlediğini söylemeden okunamaz ve bu dersin tablosu beş işlev
izliyor.
Üçüncü sınır en önemlisi. Çağrı sayısı bir işlevin kaç kez çalıştığını verir, o çalışmanın
neye mal olduğunu vermez. ilerlet 80 kez çağrıldı; bu 80 çağrının her biri bir listenin
başından bir öğe çıkarıyor, ve o işin maliyeti listenin uzunluğuna bağlı. Çağrı sayısı bunu
göstermez. Bunun için başka bir sayıya bakmak gerekir — kaç nesnenin var edildiğine ve
kaçının ayakta tutulduğuna.
Gösterge Tasarımı Bu Dersin Konusu Değil
Gözlemlenebilirlik ve Güvenilirlik kursu ile Gözlemlenebilirlik ve İşletim kursu gösterge tasarımını kurdu: hangi göstergenin toplanacağı, nasıl adlandırılacağı, hangi eşiğin uyarı üreteceği. Tekrarlanmaz, atıf yeterlidir.
Aradaki fark yön farkıdır. Gösterge tasarımı önden seçim yapar: ne toplanacağına koşum başlamadan karar verilir ve toplanmayan şey sonradan geri getirilemez. Profil çıkarma sonradan okur: koşum bittikten sonra çağrı dökümü açılır ve hangi işlevin sıcak olduğu oradan çıkar. Biri işletim aracı, öbürü tanı aracıdır; ikisi de aynı programda bulunabilir ve birbirinin yerini tutmaz.
Özet
- Belirlenimci profil aracı her çağrıyı ve her dönüşü görür; topladığı iki veriden çağrı sayısı yinelenen koşumlarda değişmez, süre alanları değişir — bu yüzden tabloda yalnız çağrı sayısı yazılır.
- Sıcak yol, en çok çağrılan koddur; uzunlukla değil çağrı sayısıyla tanımlanır ve ölçülmeden bilinemez.
- Araç yalnız çağrı sınırlarını görür; bir gövde ayrı işlev değilse tabloda satırı yoktur. Ölçümün çözünürlüğünü kodun bölünme biçimi belirler.
- Aynı 80 adımlık iş iki yükte de 80
ilerletçağrısıyla bitiyor, ama toplam çağrı 279’dan 549’a çıkıyor: sayılan şey iş değil, işe varma çabasıdır. - Sıcak yol
islemci_adimi_mi; çağrısı 136’dan 322’ye çıkıyor çünkü tek yuva geri çevirdiği her görevi sonraki tikte yeniden bakılmaya bırakıyor.
Sonraki Adım
Çağrı sayısı bir işlevin kaç kez çalıştığını söyler. Söylemediği bir şey var: o koşum
sırasında kaç nesnenin aynı anda ayakta durduğu. ilerlet iki yükte de 80 kez çağrıldı,
ama görev listelerinin bellekte kapladığı yapı iki koşumda da aynı mıydı — ve aynı 80 adım
başka bir kapta tutulsaydı kaç nesne var olurdu? Sonraki ders bu soruya bakar ve yine bayt
değil, nesne sayar.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.