Ders 06 / 09
Süreç Tabanlı Ölçekleme
İşçi sayısı koddan değil, ortamdan gelir. Ders çatallanan sürecin belleğinin ebeveyne yansımadığını, işin dilimlenip borudan toplandığını ve işçi sayısının ortam değişkeni ile çekirdek sayısından okunduğunu ölçer.
Yayın Güncelleme
Bu derste
- Çatallanan sürecin belleğinin ebeveyni etkilemediğini ölçer
- İşi işçi süreçlere dilimleyip sonucu borudan toplar
- İşçi sayısının ortam değişkeninden ve çekirdek sayısından geldiğini gösterir
- Süreç tabanlı ölçeklemede belirleyenin ortam olduğunu açıklar
İçindekiler
Önceki ders sayacın sonucunu sıranın belirlediğini gösterdi ve paylaşılan durumu kaldıran bir yol olarak Ractor’ı ölçtü. Süreç tabanlı ölçekleme aynı sonuca daha eski ve daha yaygın bir yoldan gider: her işçi kendi belleğiyle çalışan ayrı bir sürectir, bu yüzden paylaşılan değişken baştan yoktur. Geriye bir soru kalır: kaç işçi? Kursun ölçü ekseni burada da geçerlidir: bu sonucu ne belirliyor? Bu dersin gözlemi isci sayisi satırıdır ve belirleyeni ortamdır.
Bu kursta ortam kelimesi iki yerde geçer. M12/K03’te ortam, uygulamanın yapılandırma katmanıydı: geliştirme, test ve üretim. Burada ise ortam, kuralın üç girdisinden biridir: saat, çekirdek sayısı ve ortam değişkenleri. İkisi aynı ad altında farklı şeylerdir. M11/K03’te süreçlerin nasıl doğduğu ve borularla nasıl konuştuğu görülmüştü; bu ders onların sayısını kimin belirlediğini sorar. Çekirdeğin belirleyen.rb dosyası ve bu dersin kesiti isci.rb aşağıda olduğu gibi verilir. İkisi de kaynaktır, çıktısı yoktur. Çekirdek bir modeldir: ortam içindeki saat ve çekirdek sayısı gerçek makineden değil, tanımdan gelir.
# 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
# isci.rb — isci sayisi ortam degiskeninden, yoksa cekirdek sayisindan gelir
require_relative "belirleyen"
# Surec tabanli olcekleme: isci sayisi ortam degiskeninden, yoksa cekirdek sayisindan gelir.
def isci_sayisi(ortam) = Integer(ortam[:degiskenler].fetch("ISCI_SAYISI", ortam[:cekirdek]))
Çatallanan Süreç Kendi Belleğine Yazar
Çatallama (fork), çalışan sürecin bir kopyasını başlatır. Kopya, ebeveynin belleğini o ana kadarki hâliyle devralır, ama o andan sonra iki bellek ayrıdır. Çocuk bir değişkeni değiştirirse ebeveyn bunu görmez. Aşağıdaki blok bu ayrımı ölçer: çocuk deger değişkenine doksan dokuz yazar ve üç koduyla çıkar; ebeveyn değişkenin değerini ve çocuğun çıkış kodunu okur.
# cocuk.rb — cocuk kendi bellegine yazar, cikis kodu ebeveyne doner deger = 1 pid = fork do deger = 99 exit!(3) end Process.wait(pid) p deger p $?.exitstatus
1 3
Ebeveyn deger için bir görür; çocuğun yazdığı doksan dokuz ebeveyne ulaşmadı. Ebeveyne dönen tek şey çıkış koduydu. Bu, Ractor’ın zorla kurduğu yalıtımın süreçlerde işletim sisteminden bedavaya gelmesidir. RB5. Bu ölçüm çatallamanın belleği ayırdığını gösterir; ayrılan belleğin ne kadar yer tuttuğunu göstermez. O soru bir sonraki derstedir.
İşi Dilimleyip Boruyla Toplamak
Bellek ayrı olunca sonucu ebeveyne geri taşımak gerekir. Bunun yolu **boru (pipe)**dir. Aşağıdaki blok bir ile otuz arasındaki sayıları onarlık üç dilime böler, her dilimi bir çocuğa verir ve çocuklar dilim numarası ile dilimin toplamını aynı boruya yazar. Çocukların bitiş sırası belli olmadığı için ebeveyn sonuçları numaraya göre sıralayarak basar.
# dilim.rb — is uc iscide dilimlenir, sonuclar borudan toplanir
okuyucu, yazici = IO.pipe
pidler = (0..2).map do |no|
fork do
okuyucu.close
dilim = ((no * 10) + 1)..((no * 10) + 10)
yazici.puts "#{no} #{dilim.sum}"
yazici.close
exit!(0)
end
end
yazici.close
pidler.each { |pid| Process.wait(pid) }
sonuclar = okuyucu.each_line.map { |satir| satir.split.map(&:to_i) }.sort
p sonuclar
p sonuclar.sum { |_, toplam| toplam }
[[0, 55], [1, 155], [2, 255]] 465
Üç dilim elli beş, yüz elli beş ve iki yüz elli beş verdi; toplam dört yüz altmış beştir ve bir ile otuz arasındaki sayıların toplamıyla aynıdır. Sıralama ölçümün parçasıdır: sıralanmasaydı satırlar çocukların bitiş sırasıyla gelirdi ve o sıra koşumdan koşuma değişebilirdi. RB6. Üç çocuk ve ufak bir iş için ölçüldü; çocuk başlatmanın bedeli iş küçükken kazancı aşabilir ve bu bedel burada ölçülmedi.
İşçi Sayısı Nereden Gelir
İşçi sayısı koda gömülmez. Bir dağıtımda dört çekirdekli makine de olur, sekiz çekirdekli de; sayıyı o makinenin söylemesi beklenir. isci_sayisi önce ISCI_SAYISI ortam değişkenine bakar, yoksa çekirdek sayısını alır. Aşağıdaki blok bu tanımı çekirdeğin tabanında ve ortam değişkeni verilmiş bir ortamda koşturur, sonra sayı olmayan bir değeri dener.
# isci_olc.rb — isci sayisi ortamdan okunur
require_relative "isci"
p isci_sayisi(TABAN.ortam)
p isci_sayisi(TABAN.ortam.merge(degiskenler: { "ISCI_SAYISI" => "3" }))
begin
isci_sayisi(TABAN.ortam.merge(degiskenler: { "ISCI_SAYISI" => "uc" }))
rescue ArgumentError => e
puts e.class
end
p belirleyen(->(k) { isci_sayisi(k.ortam) })
p belirleyen(->(k) { k.ortam[:degiskenler].fetch("ISCI_SAYISI", "yok") })
4 3 ArgumentError ["ortam", "B- S- O+", 4] ["ortam", "B- S- O+", "yok"]
Tabanda dört çıktı: ortam değişkeni yok, çekirdek sayısı dört. Değişken üç olunca sayı üç oldu. "uc" yazısı bir tamsayıya çevrilemediği için ArgumentError doğdu; yanlış yazılmış bir değişken sessizce varsayılana düşmedi, patladı. Dördüncü satırda kural bu hesabı üç değişkende koşturdu. Yalnız ortam sonucu oynattı; kanıt B- S- O+, belirleyen ortamdır. Beşinci satır aynı soruyu çekirdek sayısına düşmeden sorar: ortam değişkeni yoksa "yok", varsa "3" döner ve belirleyen yine ortamdır.
Bu çıktı bir yapılandırma kararını da gösterir: işçi sayısının ortamdan gelmesi bir kusur değildir, bilerek yapılmış bir tercihtir. Kusur, ortamın söylediğini kodun bilmemesidir. RB7. Çekirdek sayısı modelde dörttür; gerçek bir makinede bu sayı Etc.nprocessors ile okunur ve her makinede farklıdır.
# cekirdek.rb — gercek cekirdek sayisi basilmaz, yalnizca var oldugu dogrulanir require "etc" p Etc.nprocessors.positive?
true
Gerçek sayı basılmadı çünkü her makinede farklıdır; ölçülen şey, sayının sıfırdan büyük olmasıdır.
Sınırlayıcı Ölçüm
Çatallanan süreçte de kuralın çalışıp çalışmadığı sınanmalıdır. Aşağıdaki blokta ikinci dilimin toplamı çocuk süreçte, belirleyen çağrısının içinde hesaplanır ve sonuç borudan ebeveyne Marshal ile geçer. Soru şudur: dilimin toplamını ne belirliyor?
# sinir.rb — cocuk surecin dilim toplamini kural siniflandirir
require_relative "belirleyen"
okuyucu, yazici = IO.pipe
pid = fork do
okuyucu.close
yazici.write Marshal.dump(belirleyen(->(_k) { (11..20).sum }))
yazici.close
exit!(0)
end
yazici.close
Process.wait(pid)
p Marshal.load(okuyucu.read)
["kod", "B- S- O-", 155]
Sonuç yüz elli beştir ve kanıt B- S- O-dur; belirleyen koddur. Dilimin toplamı onbir ile yirmi arasındaki sayılardan gelir; çocuğun hangi sırada bittiği, kaç çocuk olduğu ya da makinenin kaç çekirdeği olduğu toplamı değiştirmez. Süreç tabanlı ölçeklemede iki şey birbirinden ayrılır: her işçinin hesapladığı değer koda aittir, kaç işçinin çalıştığı ise ortama. Bu ayrım dağıtımda önem kazanır: aynı kod dört işçiyle de sekiz işçiyle de aynı toplamı verir, ama aynı sürede değil.
Bir sunucu çalışırken çatallama maliyetini bir kez ödemek için işçileri başlangıçta, kod yüklendikten sonra çatallar. Buna ön çatallama (preforking) denir ve çocuklar ebeveynin belleğini ilk anda paylaşıp yalnız yazdıkları sayfaları kopyalar. RB8. Ön çatallamanın bellek kazancı burada ölçülmedi; çocuğun yazdığı sayfaların kopyalandığı varsayımı bir sonraki dersteki bellek ölçümüne bağlıdır.
Özet
- Çatallanan süreç ebeveynin belleğini devralır ama kendi kopyasına yazar; ebeveyne dönen tek şey çıkış kodu ve boruya yazılandır.
- İş dilimlenip boruyla toplanabilir; çocukların bitiş sırası belirsiz olduğundan sonuçlar numaraya göre sıralanır.
- İşçi sayısının belirleyeni ortamdır: ortam değişkeni varsa ondan, yoksa çekirdek sayısından gelir ve kanıtı
B- S- O+biçimindedir. - Sayı olmayan bir ortam değişkeni
ArgumentErrordoğurur; varsayılana sessizce düşmez. - Çocuğun dilim toplamı koda aittir; kaç işçinin çalıştığı ortama.
Sonraki Adım
İşçiler ayrı bellekle çalışınca her birinin ne kadar nesne ayırdığı sorusu doğar. Bir sonraki ders aynı bloğun neden bin bir nesne ayırdığını ve ilk koşumun neden sonrakilerden pahalı olduğunu ölçer.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.