Ders 08 / 09
Profil Çıkarma
Bir programın hangi yöntemi sıcak yoldur sorusu çağrı sayısıyla sorulur ve çağrı sayısı koda aittir. Ders TracePoint ile çağrıları sayar, düzeltmenin sayıyı nasıl değiştirdiğini ölçer ve süreye dayalı sıralamanın neden ortama bağlı olduğunu gösterir.
Yayın Güncelleme
Bu derste
- TracePoint ile programın kendi yöntemlerinin çağrı sayısını çıkarır
- Sıcak yolu çağrı sayısından belirler ve belirleyeninin kod olduğunu gösterir
- Düzeltmeden sonra yeni sayımın sıcak yolu nasıl değiştirdiğini ölçer
- Çağrı sayısı ile sürenin aynı soruyu yanıtlamadığını açıklar
İçindekiler
Önceki ders bir bloğun kaç nesne ayırdığını sordu. Bu ders bir programın hangi yöntemde ne kadar çalıştığını sorar; ancak süreyle değil, çağrı sayısıyla. Kursun ölçü ekseni değişmez: bu sonucu ne belirliyor? Bu dersin gözlemi sicak yol satırıdır ve belirleyeni koddur.
Profil çıkarma (profiling), bir programın zamanının ya da çağrılarının nerede toplandığını ölçme işidir. M10/K03’te bir kodun kaç kez koştuğu ölçülmüştü. Profil çıkarma da çağrı sayar, ama burada sınıf sayının kendisi değil, sayıyı ne belirlediğidir. Karşılaştırmalı ölçüm ve profil çıkarmanın ilkeleri M10/K05’te görülmüştü; bu ders yalnız Ruby’nin standart kütüphanesindeki araçlarla, ürün ya da profil aracı kullanmadan, iki şeyi ölçer: yöntem çağrılarının sayısı ve sürenin türü. Sıcak yol (hot path), en çok çağrılan yöntemdir. Çekirdeğin belirleyen.rb dosyası ve bu dersin kesiti profil.rb aşağıda olduğu gibi verilir; ikisi de kaynaktır, çıktısı yoktur. Kesitteki sicak_yol çağrıları TracePoint ile sayar; yalnız programın kendi tanımladığı yöntemler sayılır.
# belirleyen.rb — bir kosum modeli ve bir sonucu neyin belirledigini siniflandiran yardimci
# Kosum: sonucun bagli olabilecegi uc girdi. Bu uc girdinin disinda kalan her sey koddur.
Kosum = Data.define(:kilit, :tohum, :ortam)
TABAN = Kosum.new(
kilit: { "bicimleyici" => "1.4.2", "ayristirici" => "2.0.1" },
tohum: 1,
ortam: { saat: Time.utc(2026, 10, 16, 9, 0), cekirdek: 4, degiskenler: {} }
)
# Her degisken tek bir girdiyi degistirir; otekiler tabandaki gibi kalir.
DEGISKENLER = {
bagimlilik: TABAN.with(kilit: { "bicimleyici" => "1.5.0", "ayristirici" => "2.1.0" }),
sira: TABAN.with(tohum: 2),
ortam: TABAN.with(ortam: { saat: Time.utc(2027, 3, 1, 9, 0), cekirdek: 8,
degiskenler: { "ISCI_SAYISI" => "3" } })
}
# Kural: hesap once tabanda, sonra her degiskende bir kez kosar. Sonucu degistiren ilk
# degisken (bagimlilik, sira, ortam sirasiyla) belirleyendir; hicbiri degistirmiyorsa kod.
def belirleyen(hesap)
taban = hesap.call(TABAN)
degisti = DEGISKENLER.to_h { |ad, kosum| [ad, hesap.call(kosum) != taban] }
tur = degisti.key(true)&.to_s || "kod"
kanit = degisti.map { |ad, d| "#{ad[0].upcase}#{d ? '+' : '-'}" }.join(" ")
[tur, kanit, taban]
end
# profil.rb — programin tanimladigi yontemlerin cagrilari sayilir
# Profil: programin tanimladigi yontemlerin cagrilari sayilir; en cok cagrilan sicak yoldur.
def sicak_yol
sayim = Hash.new(0)
iz = TracePoint.new(:call) { |t| sayim[t.method_id] += 1 if t.defined_class == Object }
iz.enable { yield }
sayim.max_by { |_, n| n }
end
def para_bicimle(kurus) = format("%d.%02d", kurus / 100, kurus % 100)
def satir_bicimle(satir) = "#{satir[:ad]} #{para_bicimle(satir[:birim])} #{para_bicimle(satir[:toplam])}"
def rapor(satirlar) = satirlar.map { |s| satir_bicimle(s) }.join("\n")
Çağrıları Saymak
Bir yüz satırlık rapor kurulur: her satırın bir adı, birim fiyatı ve toplamı vardır. rapor her satırı satir_bicimleye verir, o da hem birim fiyatı hem toplamı para_bicimle ile biçimler. Aşağıdaki blok bu raporu TracePoint altında koşturur ve her yöntemin çağrı sayısını çıkarır.
# sayim.rb — her yontemin cagri sayisi
require_relative "belirleyen"
require_relative "profil"
def cagri_sayimi
sayim = Hash.new(0)
iz = TracePoint.new(:call) { |t| sayim[t.method_id] += 1 if t.defined_class == Object }
iz.enable { yield }
sayim.sort_by { |ad, n| [-n, ad] }
end
satirlar = Array.new(100) { |i| { ad: "urun#{i}", birim: 1250, toplam: 1250 * (i + 1) } }
p cagri_sayimi { rapor(satirlar) }
p sicak_yol { rapor(satirlar) }
p belirleyen(->(_k) { sicak_yol { rapor(satirlar) } })
[[:para_bicimle, 200], [:satir_bicimle, 100], [:rapor, 1]] [:para_bicimle, 200] ["kod", "B- S- O-", [:para_bicimle, 200]]
para_bicimle iki yüz kez çağrıldı: yüz satırın her biri onu iki kez çağırdı. satir_bicimle yüz, rapor bir kez çağrıldı. İkinci satır sicak_yol sonucudur, yani en çok çağrılan yöntem ve sayısı. Üçüncü satır kuralı çalıştırır: kilit dosyasındaki sürümler, tohum ve ortam değişince sonuç değişmedi, belirleyen koddur. Çağrı sayısı raporun satır sayısından ve yöntemlerin birbirini çağırma düzeninden gelir; ikisi de kodun işidir. RB13. defined_class == Object süzgeci yalnız ana düzeyde tanımlı yöntemleri sayar; başka sınıflardaki yöntemler bu sayıma girmez.
Süzgeç yalnız programın kendi yöntemlerini seçer. Ruby’nin kendi yerleşik yöntemleri :call olayını üretmez, :c_call olayını üretir. Aynı raporu :c_call ile saymak, hangi yerleşik yöntemlerin sıcak olduğunu gösterir.
# yerlesik.rb — yerlesik yontemlerin cagri sayisi
require_relative "profil"
satirlar = Array.new(100) { |i| { ad: "urun#{i}", birim: 1250, toplam: 1250 * (i + 1) } }
sayim = Hash.new(0)
iz = TracePoint.new(:c_call) { |t| sayim[t.method_id] += 1 }
iz.enable { rapor(satirlar) }
p sayim.sort_by { |ad, n| [-n, ad] }.first(5)
[[:[], 300], [:%, 200], [:/, 200], [:format, 200], [:join, 1]]
En çok çağrılan yerleşik yöntem [] oldu: her satır adı, birim fiyatı ve toplamı üç kez indeksler. %, / ve format ikişer yüz kez geldi, çünkü para_bicimle iki yüz kez çağrıldı ve her çağrı bir bölme, bir kalan alma ve bir format yaptı. Sayılar kodun yapısını yansıtır; satır sayısını ikiye katlasak satıra bağlı sayıların hepsi ikiye katlanırdı. RB14. Altıncı sıra ve sonrası burada gösterilmedi; beşinci sıradaki join bir kez çağrıldı ve geri kalanlar bu sayımdan çıkmaz.
Düzeltme ve Yeni Sayım
Sıcak yol bulunduktan sonra ilk soru şudur: bu çağrıların hepsi gerekli mi? Raporda birim fiyat her satırda aynıdır ve yalnız bir kez basılması yeterlidir. Düzeltilmiş sürüm her satırda toplamı bir kez biçimler.
# duzelt.rb — para_bicimle her satirda bir kez cagrilir
require_relative "profil"
def satir_bicimle_kisa(satir) = "#{satir[:ad]} #{para_bicimle(satir[:toplam])}"
def rapor_kisa(satirlar) = satirlar.map { |s| satir_bicimle_kisa(s) }.join("\n")
def cagri_sayimi
sayim = Hash.new(0)
iz = TracePoint.new(:call) { |t| sayim[t.method_id] += 1 if t.defined_class == Object }
iz.enable { yield }
sayim.sort_by { |ad, n| [-n, ad] }
end
satirlar = Array.new(100) { |i| { ad: "urun#{i}", birim: 1250, toplam: 1250 * (i + 1) } }
p cagri_sayimi { rapor_kisa(satirlar) }
[[:para_bicimle, 100], [:satir_bicimle_kisa, 100], [:rapor_kisa, 1]]
para_bicimle artık yüz kez çağrılıyor ve satir_bicimle_kisa ile aynı sayıya düştü. Sıcak yol tek bir yöntem olmaktan çıktı: iki yöntem eşit çağrılıyor. Düzeltme çağrıyı yarıya indirdi ama yavaşın hangisi olduğu sorusunu çözmedi. RB15. Bu ölçüm çağrı sayısının yarıya indiğini gösterir; sürenin de yarıya indiğini göstermez.
Sınırlayıcı Ölçüm
Çağrı sayısı, en çok çalışan yöntemi söyler; en yavaş yöntemi söylemez. Bir yöntem yüz kez çağrılıp her çağrıda uzun sürebilir, bir başkası iki yüz kez çağrılıp her seferinde kısa sürebilir. Süre ayrı bir ölçümdür ve ayrı bir belirleyeni vardır. İki araç süreyi ölçer: Benchmark.realtime ve Process.clock_gettime(Process::CLOCK_MONOTONIC). İkisi de kayan noktalı bir sayı döndürür; sayının kendisi basılmaz çünkü her koşumda ve her makinede farklıdır.
# sure.rb — sure olcen iki arac, ikisi de Float dondurur
require "benchmark"
require_relative "profil"
satirlar = Array.new(100) { |i| { ad: "urun#{i}", birim: 1250, toplam: 1250 * (i + 1) } }
p Benchmark.realtime { rapor(satirlar) }.class
p Process.clock_gettime(Process::CLOCK_MONOTONIC).class
Float Float
İki araç da Float döndürdü; değerleri basılmadı. Bir ölçüm süreye dayanıyorsa ve sonuç “en yavaş hangisi” ise, belirleyen ortamdır: makine yüklüyse, çekirdek farklıysa ya da önbellek ısınmışsa sıra değişir. Çağrı sayısı ise kodun işidir ve bu yüzden her yerde aynı çıkar. İkisi aynı soruyu yanıtlamaz: hangisi daha çok çalışıyor sorusunun yanıtı koddadır; hangisi daha çok zaman alıyor sorusunun yanıtı makinededir.
Bu dersin sınırlayıcısı budur: sıcak yol çağrı sayısıyla belirlenir ve kod olarak sınıflandırılır, ama “en yavaş” bu sınıflamanın dışındadır. RB16. Süre ölçümü burada yalnız türüyle gösterildi; hangi yöntemin gerçekten en yavaş olduğu bu derste ölçülmedi ve çağrı sayısından çıkarılmamalıdır.
Özet
- Programın kendi yöntemlerinin çağrı sayısı
TracePointile:callolayı üzerinden sayılır; yerleşik yöntemler:c_callile ayrıca sayılır. - Sıcak yolu çağrı sayısı belirler, çağrı sayısını ise kod: kural üç değişkende de aynı yöntemi buldu.
- Düzeltmeden sonra yeni sayım yapılır; sıcak yol tek yöntemden birkaçına yayılabilir.
- Süre
Benchmark.realtimeya daProcess.clock_gettimeile ölçülür ve ortama bağlıdır; çağrı sayısı ile süre aynı soruyu yanıtlamaz.
Sonraki Adım
Kod, çağrı sayıları ve ayrılan nesneler ölçüldü; elde bir yayın çıkarılacak bir uygulama var. Son ders yayının içindeki üç şeyi, varlık adını, sürüm dizinini ve bildirimi ayrı ayrı sorgular ve her birinin belirleyenini bulur.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.