İçeriğe geç
academia.sh

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) AllocsPerOp ile ö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.

Aramak için yazmaya başlayın.

↑↓ ↵ Esc gezin · aç · kapat