Ders 10 / 16
İzleme
Yürütme izi bir koşum bilgisidir ve burada olay sayısıyla okunuyor: aynı altı kaydı işleyen iki kurulum, biri sıralı biri üç işçiyle dağıtık, aynı sayıda kayıt işleme olayı üretiyor ama farklı sayıda işçi başlangıç olayı üretiyor. Sınırlayıcı ölçüm: iz toplamak programın davranışını değiştiriyor, bu yüzden sayılar buna göre okunuyor.
Yayın Güncelleme
Bu derste
- İki kurulumun (sıralı, üç işçili havuz) ürettiği yürütme izi olay sayılarını filtreleyerek karşılaştırır
- Aynı toplam işin farklı sayıda eşzamanlı birimle yapılmasının hangi olay sayısını değiştirdiğini, hangisini değiştirmediğini ayırt eder
- İz toplamanın programın davranışını değiştirdiğini, sayıların bu sınırla birlikte okunması gerektiğini gösterir
İçindekiler
Önceki ders bir programın kaç değer ayırdığını sayarken durdu — bir programın yalnız bellek yönü. Ama bir programın çalışma zamanı bundan fazlasını içeriyor: hangi goroutine ne zaman başladı, kaç tanesi eşzamanlı çalıştı. Bu ders bu daha geniş kayda bakıyor: dilin kendi yürütme izi, bir programın koşumu sırasında olan olayları kaydediyor ve bu ders o kaydı olay sayısıyla okuyor.
Bu kursun dört dersi de aynı biçimde ilerledi: önce bir bilginin nereden geldiğini sınıflandır (kaynak, araç, koşum), sonra o sınıfa özgü bir sınırı ölç. Profil ve kaçış kararı iki ayrı sınıftaydı — biri koşum, biri araç. Ayırma sayısı yine koşumdu, ama tek bir sayıya değil dört karşılaştırmaya bakıyordu. Bu son ders koşuma dönüyor, ama sorduğu şey daha önce hiç sorulmayan bir tür: kaç ayrı yürütme biriminin gerçekten çalıştığı.
Yürütme izi de bu kursun ilk dersinden beri süren ayrımın bir örneği — koşum bilgisi: kaynağa bakan biri kaç goroutine’in gerçekten çalıştığını göremez, yalnız çalıştırıp sorabilir. Ama bu derste okunan sayı, profil ya da ayırma sayısından farklı bir şeyi yakalıyor: bellek değil, eşzamanlılığın kendisi. Bu kursun dört ölçü ekseni (M09/K03) burada kullanılmıyor; bu ders yalnız iki kurulumun ürettiği olay sayısını karşılaştırıyor.
“İzleme” sözcüğü burada dar bir anlamda kullanılıyor: dilin kendi yürütme izi mekanizması, bir profil ya da bir günlük kaydı değil. Profil “kaç değer ayrıldı” diye soruyordu; günlük kaydı (bu kursta ayrıca ele alınmayan bir başka mekanizma) uygulamanın kendi yazdığı iletileri tutuyor. Yürütme izi ikisinden de farklı: çalışma zamanının kendisinin, programın hangi anlarda hangi yürütme birimini çalıştırdığını kaydeden bir mekanizma. Bu ders o mekanizmanın yalnız uygulamaya özgü işaretleri kullanılan kısmını ölçüyor.
Neden Ham Sayı Değil, Etiketli Sayı
Bir yürütme izi yalnız programın yazdığı işleri kaydetmiyor; çalışma zamanının kendi arka plan işlerini de kaydediyor — zamanlayıcının bir iş parçacığını nasıl dağıttığı, çöp toplayıcının ne zaman bir tarama başlattığı gibi. Bu arka plan etkinliği makinenin o anki yüküne, işletim sisteminin zamanlamasına bağlı ve koşumdan koşuma değişiyor. Bu yüzden bir izin toplam olay sayısı — programın kendi yazdığı her şey dahil arka plan etkinliği de sayılırsa — aynı kaynak kodla bile iki ayrı koşumda farklı çıkabiliyor.
Bu ders bu değişkenliği ölçmüyor, ondan kaçınıyor: toplam sayı yerine, kendi seçtiği
iki etiketin (kayitBS-isle, iscibasBS) kaç kez geçtiğini sayıyor. Bu etiketler
yalnız bu programın kendi kodunda geçiyor, arka plan etkinliğinde hiç görünmüyor — bu
yüzden bu iki sayı, toplam olay sayısından farklı olarak koşumdan koşuma sabit
kalıyor. Kursun kararlılık kalıbı burada da geçerli: ölçülen sayı önce belirlenimci
olduğu kanıtlanmadan tabloya girmiyor.
Bu seçim, önceki üç dersteki tekrarlanabilirlik arayışının doğrudan devamı: profil sayısı beş koşumda tek değer verdiği için kullanılabilir sayıldı, ayırma sayısı da aynı sınavdan geçti. Yürütme izi kendi başına bu sınavı geçemiyor — ham hâliyle koşumdan koşuma değişiyor — ama izin bir bölümü, kendi etiketlediği bölüm, geçiyor. Bu ders bu yüzden izin tamamını değil, kararlı çıkan bölümünü ölçüyor.
İzi Etiketlerle Okumak
Bir yürütme izinin ham içeriği, bu kursta basılması yasak olan her şeyi taşıyor: goroutine numaraları, zaman damgaları, yığıt izleri. Bu ders bu içeriği hiç yazdırmıyor; onun yerine izin içinde kendi seçtiği iki etiketi arıyor ve yalnız bu etiketlerin kaç kez geçtiğini sayıyor.
// iz.go — iz dosyasini olusturuyor ve icindeki bir etiketin kac kez gectigini sayiyor
package main
import (
"context"
"os"
"os/exec"
"runtime/trace"
"strings"
)
const kayitEtiketi = "kayitBS-isle"
const iscibasEtiketi = "iscibasBS"
// izKaydet, verilen isi bir iz dosyasina kaydedip yolunu donduruyor; yol
// hicbir yerde basilmiyor, yalniz bu islevin icinde kullaniliyor.
func izKaydet(f func(ctx context.Context)) string {
dizin, _ := os.MkdirTemp("", "iz")
yol := dizin + "/trace.out"
dosya, _ := os.Create(yol)
trace.Start(dosya)
f(context.Background())
trace.Stop()
dosya.Close()
return yol
}
// olaySayisi, iz dosyasini iz aracina veriyor ve yalniz istenen etiketin kac
// kez gectigini sayiyor. Aracin urettigi metin hicbir zaman basilmiyor.
func olaySayisi(izYolu, etiket string) int {
cmd := exec.Command("go", "tool", "trace", "-d=parsed", izYolu)
cmd.Env = append(os.Environ(), "GOTOOLCHAIN=local")
cikti, _ := cmd.CombinedOutput()
return strings.Count(string(cikti), etiket)
}
BS7. olaySayisi, iz aracının ürettiği metni okuyor ama hiçbir zaman ekrana yazmıyor —
yalnız strings.Count ile bir alt dizginin kaç kez geçtiğini sayıyor ve bu sayıyı
döndürüyor. İz aracının kendi çıktısı goroutine numarası, zaman damgası ve tam dosya yolu
taşıyor; bunların hiçbiri programın herhangi bir noktasında bir değişkene bile atanmıyor,
doğrudan sayma işleminin içinde tüketilip atılıyor.
izKaydet de benzer bir disiplin taşıyor: iz dosyasının yolu os.MkdirTemp ile kurulan
geçici bir dizinde duruyor ve bu yol yalnız olaySayisiye geçirilmek için kullanılıyor —
hiçbir fmt.Println çağrısına girmiyor. İşlev imzası da bilinçli: f func(ctx context.Context) alarak, izlenecek işi çağırana bırakıyor. Bu sayede aynı izKaydet
işlevi hem sıralı hem havuzlu kurulumu izlemek için kullanılabiliyor — yalnız hangi
işlevin çağrıldığı değişiyor, izleme mekaniği aynı kalıyor.
İşçi Sayısı Neden Üç
Havuzlu kurulum üç işçi açıyor, altı kayıt işliyor — işçi sayısı kayıt sayısını tam bölmüyor bilerek: her işçi ortalama iki kayıt işlemek zorunda kalıyor ama kanaldan okuma sırası önceden belli değil, bu yüzden hangi işçinin kaç kayıt aldığı koşumdan koşuma değişebiliyor. Bu ders o dağılımı hiç ölçmüyor — yalnız üç işçinin başladığını sayıyor, kaçının kaç kayıt aldığını değil. Bu, kursun M09/K03’te kurduğu ve burada kullanılmayan bir eksenle (bir goroutine’in nasıl duracağı) karışmaması için bilinçli bir sınır: bu ders yalnız kaç yürütme biriminin var olduğunu ölçüyor, aralarındaki paylaşımı değil.
İki Kurulum: Sıralı ve Havuzlu
Aşağıdaki ölçüm aynı altı kaydı iki ayrı yoldan işliyor. Sıralı kurulum her kaydı çağıran
goroutine’in kendisinde, sırayla işliyor. Havuzlu kurulum aynı altı kaydı üç işçi
goroutine’e dağıtıyor; her işçi başladığında bir kez etiketli bir kayıt bırakıyor, sonra
kanaldan gelen kayıtları işliyor. Her iki kurulumda da her kayıt, trace.WithRegion ile
aynı etiketle işaretleniyor.
// olcum.go — ayni is, iki kurulum: sirali ve uc iscili havuz
package main
import (
"context"
"fmt"
"os"
"runtime/trace"
"strconv"
"sync"
)
type Kayit struct {
Ad string
Miktar int
}
var kayitlar = []Kayit{
{"vida", 1}, {"somun", 2}, {"pul", 3}, {"civi", 4}, {"civata", 5}, {"rondela", 6},
}
func isle(ctx context.Context, k Kayit) int {
sonuc := 0
trace.WithRegion(ctx, kayitEtiketi, func() {
sonuc = k.Miktar * 2
})
return sonuc
}
func sirayla(ctx context.Context) int {
toplam := 0
for _, k := range kayitlar {
toplam += isle(ctx, k)
}
return toplam
}
func havuzla(ctx context.Context, iscisi int) int {
is := make(chan Kayit)
var mu sync.Mutex
toplam := 0
var wg sync.WaitGroup
for id := 0; id < iscisi; id++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
trace.Log(ctx, iscibasEtiketi, strconv.Itoa(id))
for k := range is {
v := isle(ctx, k)
mu.Lock()
toplam += v
mu.Unlock()
}
}(id)
}
for _, k := range kayitlar {
is <- k
}
close(is)
wg.Wait()
return toplam
}
func main() {
sYol := izKaydet(func(ctx context.Context) { sirayla(ctx) })
defer os.RemoveAll(sYol)
hYol := izKaydet(func(ctx context.Context) { havuzla(ctx, 3) })
defer os.RemoveAll(hYol)
fmt.Println("-- iki kurulumda uretilen olay sayisi --")
fmt.Printf("%-10s %-22s %4d\n", "sirali", "kayit isleme olayi", olaySayisi(sYol, kayitEtiketi))
fmt.Printf("%-10s %-22s %4d\n", "sirali", "isci baslangic olayi", olaySayisi(sYol, iscibasEtiketi))
fmt.Printf("%-10s %-22s %4d\n", "havuzlu", "kayit isleme olayi", olaySayisi(hYol, kayitEtiketi))
fmt.Printf("%-10s %-22s %4d\n", "havuzlu", "isci baslangic olayi", olaySayisi(hYol, iscibasEtiketi))
}
-- iki kurulumda uretilen olay sayisi -- sirali kayit isleme olayi 12 sirali isci baslangic olayi 0 havuzlu kayit isleme olayi 12 havuzlu isci baslangic olayi 3
BS8. isle, her iki kurulumda da aynı işlevle çağrılıyor ve her çağrı bir
trace.WithRegion bloğuna sarılıyor — bu, izin kayitEtiketi ile işaretlenmiş bir
başlangıç ve bir bitiş olayı üretmesine yol açıyor. Altı kayıt işlendiğinde bu on iki
olaya karşılık geliyor (altı başlangıç, altı bitiş) — kurulum ne olursa olsun, çünkü her
iki kurulumda da toplam altı kayıt işleniyor.
havuzla işlevinde her işçi, kanaldan okumaya başlamadan önce trace.Log çağrısını
yapıyor — döngünün içinde değil, döngüden hemen önce, bir kez. Bu sıralama bilinçli: eğer
etiket her kayıt için tekrar bırakılsaydı, işçi başlangıç sayısı da işlenen kayıt sayısına
bağlı çıkardı ve iki farklı bilgi (kaç işçi var, kaç kayıt işlendi) birbirine karışırdı.
Etiketi yalnız bir kez, işçi hayata geçtiğinde bırakmak, bu iki sayıyı birbirinden
bağımsız tutuyor.
Sayıların Okunması
Tablo iki farklı davranış gösteriyor. kayit isleme olayi her iki kurulumda da on iki —
sıralı ya da havuzlu, işlenen kayıt sayısı aynı olduğu için üretilen olay sayısı da aynı.
Bu satır, izin neyi değiştirmediğini gösteriyor: iş dağıtımının kendisi, yapılan işin
toplam miktarını değiştirmiyor.
isci baslangic olayi ise sıralı kurulumda sıfır, havuzlu kurulumda üç. Sıralı
kurulum hiç ek goroutine açmıyor, bu yüzden hiç işçi başlangıç olayı üretmiyor; havuzlu
kurulum üç işçi açıyor ve her biri başladığında bir kez bu etiketi bırakıyor. Bu satır,
izin neyi gösterdiğini açıklıyor: kaç ayrı yürütme biriminin gerçekten çalıştığı —
kaynaktaki for id := 0; id < iscisi; id++ döngüsünün üç kez döndüğü bir insan tarafından
da sayılabilir, ama iz bunu çalışan programdan doğrudan okuyor, kaynağı ayrıca
incelemeye gerek kalmadan.
Bu iki satırın bir arada okunması, tek başına hiçbirinin veremeyeceği bir bilgi veriyor.
Yalnız kayit isleme olayi okunsaydı iki kurulumun aynı işi yaptığı görülür ama nasıl
yaptığı bilinmezdi. Yalnız isci baslangic olayi okunsaydı havuzlu kurulumun üç işçi
açtığı görülür ama bu üç işçinin gerçekten iş yapıp yapmadığı bilinmezdi — üçü de boşta
kalıp hiçbir kayıt işlememiş olabilirdi. İkisi birlikte üçüncü bir soruyu yanıtlıyor: üç
işçi açıldı ve altı kaydın tamamı gerçekten işlendi, hiçbiri kaybolmadı, hiçbiri iki
kez işlenmedi. Bu, kanaldan okuma düzeneğinin doğru çalıştığının bir kanıtı — testin
verdiği “geçti” kararından farklı bir yoldan, ama aynı türden bir güvence.
Kursun açılışı on bir bilgiyi kaynak, araç ve koşum diye ayırmıştı; bu ders o üç sınıftan yalnız koşumu kullanıyor — hem etiketli bölgeler hem işçi başlangıçları ancak program gerçekten çalıştırılıp izlendiğinde ortaya çıkıyor.
Sınırlayıcı Ölçüm: İz Toplamak Davranışı Değiştirir
Bu ders, kursun süre yasağını başka bir açıdan tekrar karşılıyor. trace.Start çağrısı
programın her goroutine geçişini, her kilitlenmeyi kaydetmeye başlıyor — bu kaydın kendisi
bedava değil, her olay işlenip yazılırken programın normal akışına ek bir iş biniyor. Bu
yüzden bir programı izlerken görülen davranış, izlenmeyen hâlinden her zaman biraz
farklı: iz toplamak programın davranışını değiştiriyor.
Bu iddia burada ölçülmüyor — ölçülmesi süreye bakmayı gerektirirdi ve süre bu kursun hiçbir dersinde yazılmıyor. Bunun yerine bir cümleyle yazılıp gerekçelendiriliyor, tıpkı karşılaştırmalı ölçüm dersinin süre yasağını gerekçelendirdiği gibi: iz toplamanın kendisi bir iş ve bu iş sıfır değil. Bu yüzden yukarıdaki tablo, izlenmeyen bir programın kaç olay ürettiğini değil, izlenen bir programın kaç olay ürettiğini gösteriyor — sayılar bu çerçevede okunmalı, izleme kapalıyken de aynı davranışın süreceği varsayılmadan.
Bu sınır, önceki üç dersin sınırlarıyla aynı aileden. Profil yalnız koşan yolu görüyordu; kaçış çözümlemesi yalnız bu derleyicinin kararını gösteriyordu; ayırma sayısı bir sayının düşmesinin otomatik bir iyileştirme olmadığını öğretmişti. Bu ders de aynı örüntüyü sürdürüyor: bir ölçüm aracı, ölçtüğü şeyi ölçme eyleminin kendisiyle birlikte sunuyor ve okuyan taraf bu birlikteliği unutursa yanlış bir kesinlik hissine kapılıyor. İz toplamanın davranışı değiştirmesi, iz aracının bir kusuru değil — herhangi bir gözlem aracının, gözlemlediği sistemin bir parçası hâline gelmesinin kaçınılmaz bir sonucu.
Bu Ölçümün Kapsamı
Gerçek bir üretim izinin okuduğu şey bu dersteki iki etiketten çok daha geniş: bir zamanlayıcının bir goroutine’i ne zaman durdurup başka birini çalıştırdığı, bir goroutine’in ne kadar süre bir kilidi beklediği, bir çöp toplama turunun ne zaman başladığı — bunların hepsi aynı izin içinde duruyor ve bir iz görüntüleyicisiyle zaman çizelgesi olarak incelenebiliyor. Bu tür bir inceleme genelde belirli bir soruna yanıt arıyor: neden bu istek beklenenden uzun sürdü, hangi goroutine hangi kaynağı tıkadı. Bu sorular süre içeriyor, bu yüzden bu kursta hiç sorulmuyor.
Bu derste kurulan yöntem bunun yerine dar bir soruya cevap veriyor: belirli bir olay kaç
kez oldu. Bu, gerçek bir izin sunduğu bilginin küçük bir dilimi — zaman çizelgesindeki
sıralamayı, çakışmayı, bekleme süresini hiç görmüyor. Ama bu dar soru bile kaynağa
bakılarak yanıtlanamıyor: havuzla işlevini okuyan biri üç goroutine açıldığını görebilir,
ama bu üçünün gerçekten çalışıp çalışmadığını, gerçekten iş alıp almadığını yalnız
çalıştırıp izleyerek öğrenebilir.
Özet
- Yürütme izi bir koşum bilgisidir ve bu derste olay sayısıyla okundu; goroutine numarası, zaman damgası ve yığıt izi hiçbir yerde basılmadı.
- Aynı altı kaydı işleyen sıralı ve havuzlu iki kurulum, aynı sayıda (on iki) kayıt işleme olayı üretti — iş dağıtımı toplam işin miktarını değiştirmedi.
- İki kurulum farklı sayıda (sıfır, üç) işçi başlangıç olayı üretti — bu fark, kaç ayrı yürütme biriminin gerçekten çalıştığını izin doğrudan göstermesinden geliyor.
- İz aracının ürettiği metin hiçbir zaman basılmadı; yalnız seçilen iki etiketin kaç kez geçtiği sayıldı.
- İz toplamak programın davranışını değiştiriyor; bu ders bunu ölçmedi, bir cümleyle yazıp sayıları bu sınırla birlikte sundu.
- Yürütme izinin ham içeriği koşumdan koşuma değişiyor; bu ders bu yüzden izin tamamını değil, kendi etiketlediği ve kararlı çıkan bölümünü ölçtü.
Sonraki Adım
Bu kursun dört dersi de bir programı koşturarak bir şey öğrendi — profil, kaçış kararı, ayırma sayısı, olay sayısı; hepsi ancak çalışan bir programdan okunabildi ve her biri kendi ölçüm eyleminin programı biraz değiştirdiğini kabul etmek zorunda kaldı. Sıradaki konu bu zeminin tamamından çıkıyor: hangi dosyanın derlemeye gireceğini, hangi platforma çıkılacağını ve ikiliye ne gömüleceğini araç, programı hiç çalıştırmadan söylüyor — koşumun kendisi artık gerekmiyor.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.