Ders 07 / 16
Profilleme
Profil bir koşum bilgisidir ve bu derste sayıyla okunur: aynı işlevin iki çağrı biçiminde kaç değer ayrıldığı. Sınırlayıcı ölçüm: profil yalnız koşan yolu görüyor — hiç çalışmayan bir dalın ayırdığı değerler profilde hiç görünmüyor, kaynakta yazılı olsalar bile.
Yayın Güncelleme
Bu derste
- Aynı işlevin iki çağrı biçiminin ayırma sayılarını AllocsPerOp ile ölçüp karşılaştırır
- Hiç çalışmayan bir dalın ayırdığı değerlerin profilde hiç görünmediğini koşturarak gösterir
- Profilin "nerede ayrıldığını" yanıtladığını, "neden yavaş olduğunu" yanıtlamadığını ayırt eder
İçindekiler
Önceki ders kapsama oranının koşulan satırları saydığını, doğru sınandığını söylemediğini gösterdi. Bu ders aynı soruyu belleğe soruyor: bir program çalışırken nerede ayırma yaptığı kaynağa bakılarak görülemiyor, ama koşum bittiğinde bir profil bunu sayıyla söylüyor. Profil de kapsama gibi bir koşum bilgisi — kaynağı okuyan biri hangi işlevin kaç değer ayırdığını göremez, yalnız çalıştırıp sorabilir.
Bu paylaşılan biçim tesadüfi değil: kursun açılışından beri her ders aynı iskeleti kullanıyor — bir bilginin kaynakta mı, araçta mı, koşumda mı durduğunu önce sınıflandırmak, sonra o sınıfa özgü sınırı ölçmek. Profilin buradaki yeri nettir: kapsama gibi, yalnız koşarak öğreniliyor. Ama kapsamadan farklı olarak, profilin okuduğu şey bir doğru/yanlış kararı değil bir miktar — bu farkın ölçüme nasıl yansıdığı bu dersin konusu.
Eşzamanlılık kursu (M09/K03) profil çıkarmayı ve bir ölçüm disiplinini zaten kurdu: bir ölçüm, neyi saydığını söylemeden karşılaştırılamaz. Bu ders o disiplini Go’nun kendi araçlarıyla sürdürüyor; kursun ekseni (örtüşen adım) burada kullanılmıyor. Buradaki soru dar: bir profil size nereyi söylüyor, neyi söylemiyor?
Üç Profil Türü, Bir Sayı
Katalog bu konuyu üç profil türüyle tanımlıyor: işlemci profili, bellek profili ve engelleme profili. Üçü de dilin kendi ölçüm paketiyle üretilebiliyor ama üçü aynı türde bilgi taşımıyor. İşlemci profili, programın zamanını hangi işlevde geçirdiğini örnekleyerek ölçüyor — bu ölçümün doğası gereği süredir ve süre bu kursun hiçbir dersinde yazılmıyor, basılmıyor, ölçülmüyor. Engelleme profili de benzer bir örnekleme yapıyor: bir goroutine’in ne kadar süre bir kilit ya da kanal beklediğini ölçüyor, yine süre temelli.
Bellek profili farklı bir soruya yanıt veriyor: kaç değer ayrıldığı, ne kadar süre geçtiği değil. Bu, bu kursun sayabildiği tek profil türü — sayısı bir koşumdan diğerine sabit kalıyor, süre gibi makineye ve o anki yüke bağlı değil. Bu ders bu yüzden yalnız bellek profiline ve onun da tek bir özetine — işlem başına ayırma sayısına — odaklanıyor. İşlemci ve engelleme profilleri var olmaya devam ediyor; burada ölçülmüyorlar, çünkü ölçtükleri şey süre.
Profil: Kaç Değer, Hangi Çağrıda
Bu derste “profil” sözcüğü tek bir sayıya indiriliyor: bir çağrının işlem başına kaç değer ayırdığı. Bu sayı, dilin kendi ölçüm paketiyle program içinden okunuyor — bellek baytı, süre ya da yığıt izi hiç basılmıyor.
// profil.go — ayni islevin iki cagri bicimi, profilde kac deger ayirdigini gosteriyor
package main
import "fmt"
type Kayit struct {
Ad string
Miktar int
}
// bicimle bir kaydi tek satira ceviriyor. ayrintili istendiginde ekstra bir
// alan dizisi olusturuyor; istenmediginde o govde hic calismiyor.
func bicimle(k Kayit, ayrintili bool) string {
satir := fmt.Sprintf("%s:%d", k.Ad, k.Miktar)
if ayrintili {
alanlar := make([]string, 0, 3)
alanlar = append(alanlar, fmt.Sprintf("ad=%s", k.Ad))
alanlar = append(alanlar, fmt.Sprintf("miktar=%d", k.Miktar))
alanlar = append(alanlar, fmt.Sprintf("uzunluk=%d", len(k.Ad)))
for _, a := range alanlar {
satir += " " + a
}
}
return satir
}
BS1. bicimle tek bir işlev ama iki ayrı yol taşıyor: ayrintili yanlışken yalnız
ilk satır çalışıyor, doğruyken üç ayrı biçimlendirme ve bir birleştirme döngüsü ekleniyor.
Kaynağı okuyan biri iki yolun da var olduğunu görür ama hangisinin ne kadar ayırdığını
göremez — bu, koşturmadan bilinemeyen bir bilgi.
İki Çağrı Biçimini Ölçmek
bicimlenin kendisi hiçbir şey ölçmüyor — yalnız bir sonuç üretiyor. Ölçüm ayrı bir katmanda
duruyor: dilin kendi karşılaştırmalı ölçüm işlevi (func(*testing.B)) çağrıyı b.N kez
tekrarlıyor; ölçüm paketi bu tekrarlar sırasında kaç değerin öbeğe taşındığını sayıyor ve sonuçta
işlem başına ortalama bir sayı veriyor. Aşağıdaki dosya iki ayrı karşılaştırmalı ölçüm işlevi
tanımlıyor — biri ayrintili=false, öbürü ayrintili=true çağırıyor — ve ikisini de beşer kez
koşturuyor.
// olcum.go — ayni islevin iki cagrisi, bes kosumda ayirma sayisi
package main
import (
"fmt"
"testing"
)
var ornekKayit = Kayit{Ad: "vida", Miktar: 12}
func profilAyrintisiz(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = bicimle(ornekKayit, false)
}
}
func profilAyrintili(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = bicimle(ornekKayit, true)
}
}
func besKosum(f func(*testing.B)) (int64, int) {
degerler := map[int64]bool{}
var ilk int64
for i := 0; i < 5; i++ {
r := testing.Benchmark(f)
degerler[r.AllocsPerOp()] = true
if i == 0 {
ilk = r.AllocsPerOp()
}
}
return ilk, len(degerler)
}
func main() {
fmt.Println("-- profil: kac deger ayrildi --")
ayrintisiz, ad1 := besKosum(profilAyrintisiz)
ayrintili, ad2 := besKosum(profilAyrintili)
fmt.Printf("%-24s ayirma=%-3d bes kosumda ayri deger=%d\n", "ayrintisiz cagri", ayrintisiz, ad1)
fmt.Printf("%-24s ayirma=%-3d bes kosumda ayri deger=%d\n", "ayrintili cagri", ayrintili, ad2)
fmt.Println("fark:", ayrintili-ayrintisiz, "ayirma, yalniz ayrintili dala ait")
}
-- profil: kac deger ayrildi -- ayrintisiz cagri ayirma=2 bes kosumda ayri deger=1 ayrintili cagri ayirma=9 bes kosumda ayri deger=1 fark: 7 ayirma, yalniz ayrintili dala ait
BS2. besKosum, kursun her başarım dersinde geçerli olan kalıbı burada da uyguluyor:
aynı ölçüm beş kez tekrarlanıyor ve kaç ayrı değer geldiği sayılıyor. Süre burada hiç
okunmuyor; testing.Benchmarkin döndürdüğü sonuçtan yalnız AllocsPerOp alınıyor. Beş
koşumun beşinde de tek bir değer çıkması, bu sayının koşumdan koşuma sabit olduğunu — süre
gibi ortama bağlı olmadığını — gösteriyor.
degerler bir map[int64]bool olarak tutuluyor, tek bir sayaç değil; bunun nedeni beş
koşumun aynı değeri verip vermediğini kanıtlamak, varsaymamak. Beş koşum aynı sayıyı
verirse degerler tek bir anahtar taşır ve len(degerler) bir çıkar; farklı bir sayı
gelseydi ikinci bir anahtar eklenir ve bu sayı ikiye çıkardı — tıpkı kursun ortak
çekirdeğinde süre ölçümünün sekiz koşumun ikisinde farklı çıkıp çerçeveden çıkarılması gibi.
Ayırma sayısı için bu hiç olmuyor; aşağıdaki çıktıda iki satırın da ayri deger=1 vermesi bu
iddiayı doğruluyor.
Bir İşlevin İçini Profillemek, Bir Servisi Profillemek
Yukarıdaki ölçüm bir profil aracının küçültülmüş bir biçimi. Üretimde çalışan bir servisi profillemek, gerçek trafik altında hangi işlevin ne kadar bellek ayırdığını örnekleyerek ölçüyor — servis saatlerce çalışıyor, profil aracı bu süre boyunca ayırmaları topluyor ve sonunda bir dağılım veriyor. Bu dağılım gerçek kullanıcı girdisini yansıtıyor ama koşumdan koşuma aynı çıkmıyor: bir gün daha çok ayrıntılı kayıt gelirse profil de değişiyor.
Bu derste kullanılan yöntem farklı bir şey yapıyor: tek bir işlevi, tek bir girdiyle, beş kez koşturup sabit bir sayı arıyor. Bu, bir servisin profilini çıkarmanın yerini tutmuyor — küçük, yalıtılmış ve yeniden üretilebilir bir ölçüm. Aradaki fark önemli: üretim profili “gerçekte ne oluyor” sorusuna yanıtlıyor ve yanıtı trafiğe göre değişiyor; buradaki ölçüm “bu belirli girdiyle bu işlev ne yapıyor” sorusuna yanıtlıyor ve yanıtı sabit. İkisi de profil sözcüğünü taşıyor, ikisi de koşum bilgisi, ama biri tekrarlanabilir bir deney, öbürü sürekli bir gözlem.
Bu daraltmanın bir bedeli var: yalıtılmış bir işlevin ölçümü, o işlevin gerçek bir servis
içindeki payını göstermiyor. bicimle bir isteğin yüzde biri kadar zaman alıyor olabilir ya
da yüzde doksanı — bu ders bunu hiç söylemiyor, çünkü ölçüm işlevi bağlamından koparıyor. Bir
üretim profilinin asıl gücü tam olarak burada: hangi işlevin toplam ayırmanın ne kadarını
oluşturduğunu, bütün program çalışırken gösteriyor. Bu derste kurulan yöntem, o büyük resmin
içindeki tek bir karoyu büyüteçle incelemenin bir modeli — büyük resmin kendisi değil.
Profil ile Karşılaştırmalı Ölçüm Arasındaki Fark
Kursun ilerleyen bir dersi de aynı ölçüm aracını (testing.Benchmark, AllocsPerOp)
kullanıyor ama farklı bir soru soruyor: aynı işin iki ayrı yazımı hangisi daha az
ayırıyor. Bu ders o soruyu sormuyor — burada tek bir işlev var, bicimle ve iki çağrı
biçimi onun iki dalını ayırıyor. Karşılaştırmalı ölçüm iki ayrı gerçekleştirimi kıyaslar;
profilleme tek bir gerçekleştirimin neresinin ayırdığını gösterir. Araç aynı, soru farklı:
biri “hangisi daha iyi” diye soruyor, öbürü “nerede” diye soruyor. Bu ayrım önemli çünkü aynı
sayı (AllocsPerOp) iki bağlamda da çıkıyor ve hangi soruya yanıt verdiği yalnız ölçümün
kurulumundan anlaşılıyor.
Sayıların Okunması
ayrintisiz çağrı iki değer ayırıyor: fmt.Sprintf("%s:%d", ...) çağrısının kendisi bir
dizgi üretiyor ve bunu yaparken en az bir ara değeri öbeğe taşıyor. ayrintili çağrı dokuz
değer ayırıyor. Aradaki yedi ayırma tümüyle if ayrintili bloğuna ait: üç ayrı
fmt.Sprintf çağrısı, bir dilim ayırması ve döngü içindeki birleştirmelerin bir kısmı.
Kaynakta bu iki yol da aynı işlevin gövdesinde yan yana duruyor; profil olmadan hangisinin
ne kadar ayırdığı bilinemez.
Bu, profilin yanıtladığı soruyu netleştiriyor: nerede ayrılıyor. Hangi çağrı biçimi daha çok değer ayırıyor, ölçümden doğrudan okunuyor. Ama profil neden yavaş olduğunu söylemiyor — bu derste hiç ölçülmeyen bir soru, çünkü yanıtı süreye bakmayı gerektiriyor ve süre bu kursta hiçbir derste yazılmıyor. Ayırma sayısının yüksek olması programın yavaş çalıştığı anlamına gelmez; yalnız o yolun daha çok bellek istediği anlamına gelir. İkisi genelde birlikte gider ama profilin kendisi bu ikisini ayırmıyor — ayırma sayısını okuyan kişi ayırıyor.
ayrintisiz çağrının iki değer ayırması da başlı başına bir okuma dersi. Kaynakta tek bir
fmt.Sprintf çağrısı görünüyor, ama bu tek satır görünenden fazla adımı gizliyor: Sprintf
yeni bir dizgi üretiyor, üstelik k.Miktar gibi bir tam sayıyı değişken sayıda argüman alan
parametresine (...any) geçirirken onu bir arayüz değerine sarıyor. Kaynakta hiçbir
işaretçi, hiçbir new, hiçbir append görünmediği hâlde iki ayrı ayırmaya yol açan tam
olarak bu iki adım — dizgi üretimi ve arayüze sarma. Hangi değerin ne zaman böyle sarıldığı,
dilin kendi çağrı biçiminin bir sonucu ve kaçış çözümlemesinin verdiği bir karar. O kararın
kendisi sıradaki dersin konusu; bu ders yalnız kaç değerin ayrıldığını sayıyor, hangi
kararın buna yol açtığını değil.
Kursun açılışı on bir bilgiyi kaynak, araç ve koşum olarak üçe ayırmıştı; bu ders o üç sınıftan yalnız koşumu kullanıyor, çünkü profil sayısı ancak çalıştırılarak öğreniliyor.
Sınırlayıcı Ölçüm: Koşmayan Yol Profilde Yok
Yukarıdaki tabloda ayrintisiz satırı iki değer gösteriyor — üç Sprintf çağrısının,
dilim ayırmasının ve birleştirme döngüsünün hiçbiri bu satıra girmiyor. Bunun nedeni bu
değerlerin küçük olması ya da derleyicinin onları optimize etmesi değil: ayrintili
yanlış geçildiğinde if bloğunun gövdesi hiç çalışmıyor. Kaynakta yedi ayırmaya
karşılık gelen kod duruyor, ama profilAyrintisiz onu hiç tetiklemiyor; profil de
tetiklenmeyen hiçbir şeyi göremiyor.
BS3. Bu, profil çıkarmanın kapsama oranıyla (önceki ders) paylaştığı sınır: ikisi de
yalnız koşan yolu ölçüyor. Kapsama oranı koşulmayan satırı saymıyordu; profil koşulmayan
dalın ayırmasını saymıyor. Bir dalın “az ayırdığı” sonucuna varmadan önce o dalın gerçekten
tetiklendiğinden emin olmak gerekiyor — aksi hâlde ölçülen şey o dalın davranışı değil,
hiç çalışmamış olmasıdır. Fark biçimindeki benzerlik de kayda değer: kapsama oranı
koşulmayan satırı sıfır sayarak görmezden geliyordu, profil de koşulmayan dalın
ayırmasını sıfır sayarak görmezden geliyor — ikisi de “yok” ile “ölçülmedi”yi aynı
sembolle (0) yazıyor ve okuyan tarafın bu ikisini birbirinden ayırması gerekiyor.
Bu sınır pratikte iki yönlü çalışıyor. Bir yandan, seyrek çalışan bir dal (örneğin yalnız
hata durumunda giren bir kod yolu) günlük profillerde hiç görünmeyebilir ve gözden kaçabilir.
Öte yandan, iki çağrı biçimini karşılaştırırken kullanılan girdinin her iki dalı da gerçekten
tetiklediğinden emin olunmalı; aksi hâlde karşılaştırma yanlış yola bakıyor demektir. Yukarıdaki
ölçümde profilAyrintisiz yalnız ayrintili=false, profilAyrintili yalnız ayrintili=true
çağırıyor — bu seçim bilinçli, çünkü karşılaştırmanın amacı tam olarak bu iki dalı ayrı ayrı
görmek.
Aynı sınır, tek bir çağrı biçiminin içinde de geçerli. bicimle yalnız iki durumla
çağrıldı: ayrintili ya tümüyle doğru ya tümüyle yanlış. Gerçek bir çağıran kümesi bu
işlevi karma bir dağılımla çağırabilir — kayıtların onda biri ayrıntılı, geri kalanı değil —
ve o dağılımın ürettiği ortalama ayırma sayısı yukarıdaki iki uçtan hiçbirine eşit olmaz.
Profil, ölçüldüğü girdiyi anlatıyor; başka bir girdi dağılımının profili başka bir tablo
üretir. Bu yüzden bir profil sayısı, hangi girdiyle üretildiği yazılmadan aktarılamaz —
tıpkı önceki dersteki kapsama oranının hangi test kümesiyle üretildiği yazılmadan
aktarılamaması gibi.
Sayı Neye Karar Verdirmemeli
İki satırlık tablo tek başına bir eylem önermiyor. ayrintili çağrının yedi değer daha çok
ayırması, ayrintili dalının kaldırılması gerektiği anlamına gelmiyor — o dal muhtemelen
gerçek bir ihtiyacı karşılıyor, üç ayrı alanı ayrı ayrı biçimlendirmek başka bir yerde
gerçekten kullanılıyor olabilir. Profil sayısı yalnız nerede ayrıldığını gösteriyor;
o ayırmanın gerekli olup olmadığına, hangi çağıranın hangi sıklıkla hangi dalı kullandığına
bakmadan karar verilmiyor. Bu, sıradaki dersin de tekrar edeceği bir uyarı: bir sayının
küçülmesi otomatik olarak bir iyileşme değil, çünkü sayı tek başına neyin karşılığında
küçüldüğünü söylemiyor.
Bu, kursun ilk dersinden beri süren bir kalıbın da tekrarı: bir aracın söylediği bilgi, aracın neyi ölçtüğüyle sınırlı. Kapsam aracı yalnız hangi satırın koştuğunu söylüyordu, doğruluğu değil; profil aracı yalnız hangi çağrının kaç değer ayırdığını söylüyor, o ayırmanın gerekip gerekmediğini değil. Her iki durumda da sınırı aşan yorum, aracın kendisinden değil, okuyan kişinin aracın sınırını unutmasından geliyor.
Özet
- Profil bir koşum bilgisidir: hangi çağrının kaç değer ayırdığı kaynağa bakılarak değil, yalnız çalıştırılarak öğreniliyor.
- Aynı işlevin iki çağrı biçimi (
ayrintisiz,ayrintili)AllocsPerOpile ölçüldü; ayrıntılı çağrı yedi değer daha çok ayırdı ve bu fark beş koşumda değişmedi. - Profil nerede ayrıldığını yanıtlıyor, neden yavaş olduğunu yanıtlamıyor — süre bu kursta hiçbir derste ölçülmüyor.
- Koşmayan bir dalın ayırdığı değerler profilde hiç görünmüyor; kaynakta duran kod bunu değiştirmiyor, yalnız o yolun gerçekten çalışması değiştiriyor.
- Bu sınır, önceki dersteki kapsama oranının sınırıyla aynı biçimde: ikisi de yalnız koşan yolu ölçüyor.
Sonraki Adım
Profil bir değerin nereye ayrıldığını koşumdan sonra söylüyor. Ama bir değerin yığıtta mı öbekte mi duracağı kararı koşumdan çok önce, kaynak daha derlenirken veriliyor — bu ders bu kararın sonucunu sayıyla gördü, kararın kendisini hiç görmedi. Sıradaki ders bu kararın mekanizmasına bakıyor: derleyici bu kararı nasıl bildiriyor, hangi kalıplar bir değeri öbeğe taşıyor ve aynı işlevin küçük bir değişiklikle kararı nasıl değiştiği.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.