Ders 06 / 10
Yürütücü Havuzları
Aynı arayüz iki arka uçta aynı görevi ayrı sayılarla bitirir: sekiz genişlikte iş parçacığı havuzu üç yükte 31, 42 ve 73 tik verirken süreç havuzu üçünde de 10 tik ve 70 örtüşen adım verir; arayüzün aynı olması seçimin de aynı olduğu anlamına gelmez.
İçindekiler
Önceki ders engelleyen bir çağrıyı bir yürütücü havuzuna devretti ve havuzun genişliğini hiç sormadı; ölçümde havuz sınırsız sayılmıştı. Gerçek bir havuzun genişliği vardır ve arkasında ne olduğu — iş parçacığı mı, süreç mi — ölçümü değiştirir.
Bu ders o soruyu sorar. Yürütücü havuzu (executor pool) iş dağıtmanın ortak arayüzüdür: aynı çağrı biçimi, arkasında iş parçacığı da olsa süreç de olsa aynı yazılır. Bu dersin ölçtüğü şey arayüzün altındaki fark: aynı görev, aynı genişlik, iki arka uç — hangi sayılar çıkar?
Ortak Arayüz
Yürütücü havuzu üç şey verir. Bir iş gönderilir ve karşılığında sonuç nesnesi alınır; bir işlev bir dizi girdi üzerinde eşlenir ve sonuçlar girdi sırasıyla döner; havuz kapatılır ve gönderilmiş işler tamamlanır. Bu üç işlem iki arka uçta da aynı adla, aynı imzayla çağrılır.
Arayüzün taşıdığı bir güvence bu dersin ölçüm koşulunu da veriyor: eşleme işleminin çıktısı girdinin sırasına bağlıdır, çizelgelemeye değil. Hangi işin hangi işçide ne zaman koştuğu belirsiz olsa da sonuç listesi belirlenimcidir. Aşağıdaki ilk blok bunu gösteriyor.
Kuyruk, arka plan işçisi ve geri basınç Önbellekleme, Kuyruklar ve Eşzamansız İşleme kursunda servis tarafında kuruldu; burada tekrarlanmaz. Buradaki havuz bir servis bileşeni değil, tek bir programın içindeki bir iş dağıtım arayüzüdür.
Arayüz Aynı, Arka Uç İki Sayı
Modelin diliyle iki arka uç arasındaki fark tek bir yerde toplanıyor.
İş parçacığı arka ucu genişlik kadar işçi verir ama tek bir yorumlayıcının içindedir; yuva birde kalır. Süreç arka ucu genişlik kadar işçi verir ve her biri ayrı bir yorumlayıcı olduğu için yuva da genişlik kadardır.
İkinci dersin ve üçüncü dersin sonuçları bu tanımın içinde duruyor. Yeni olan şey, ikisinin aynı arayüzün arkasında olması: çağıran taraf tek bir satır değiştirerek birinden öbürüne geçer ve sayılar değişir.
Ölçümün varsayımları:
- EM59 — Görev kurgusu önceki derslerin aynısıdır: sekiz görev, görev başına on adım, toplam seksen adım; üç yük yalnız girdi/çıktı payıyla ayrılır.
- EM60 — İş parçacığı havuzunun modeldeki karşılığı işçi = genişlik, yuva = 1’dir.
- EM61 — Süreç havuzunun modeldeki karşılığı işçi = genişlik, yuva = genişlik’tir.
- EM62 — Gerçek süreç başlatılmaz. Gerçek iş parçacığı yalnız arayüzü göstermek için kullanılır ve verilen iş saftır: aynı girdiye aynı sonucu verir, yan etkisi yoktur ve çıktının sırası eşleme işleminin sözleşmesinden gelir, çizelgeden değil.
- EM63 — Ölçüm hiçbir yerde süre saymaz; havuzun karşılaştırıldığı birim tik ve örtüşen adımdır.
- EM64 — Görevler bağımsızdır; havuza gönderilen işler birbirini beklemez.
- EM65 — Havuza iş göndermenin kendisi bu ölçümde bedelsizdir.
- EM66 — Süreç arka ucunun veri devri bedeli bu ölçüme dahil değildir; o bedel üçüncü derste ayrıca ölçüldü. Buradaki süreç sütunu bu yüzden bir üst sınırdır.
- EM67 — Genişlik taraması aynı görev dizisiyle yapılır; yalnız genişlik değişir.
- EM68 — Genişlik hiçbir koşumda görev sayısını aşmaz.
- EM69 — Son satırdaki sayım, üç yükün verdiği ayrı tik sayısıdır; kaç ayrı sonuç çıktığını sayar, sonuçların büyüklüğünü değil.
- EM70 — Ölçüm tek koşumdur ve tohum sabittir.
Ölçüm
"""Yurutucu havuzlari: ayni arayuz, iki arka uc, ayni gorevde ayri ortusme.""" from concurrent.futures import ThreadPoolExecutor TOHUM = 20260817 ISLEMCI, GC = "islemci", "g_c" def uretec(tohum): d = tohum % 2147483646 + 1 def r(n): nonlocal d d = (d * 48271) % 2147483647 return d % n return r def gorevler(sayi=8, adim=10, gc_payi=7, tohum=TOHUM): """Her gorev bir adim dizisi; gc_payi/10 orani girdi/cikti adimi.""" r, liste = uretec(tohum), [] for i in range(sayi): liste.append([GC if r(10) < gc_payi else ISLEMCI for _ in range(adim)]) return liste def kosum(gorevler, isci, islemci_yuvasi): """Donen: tik, ortusen adim, islemci adimi, g/c adimi.""" kalan = [list(g) for g in gorevler] tik = ortusen = islemci_adim = gc_adim = 0 while any(kalan): etkin = [i for i, g in enumerate(kalan) if g][:isci] if not etkin: break yuva, ilerleyen = islemci_yuvasi, 0 for i in etkin: adim = kalan[i][0] if adim == ISLEMCI: if yuva <= 0: continue yuva -= 1 islemci_adim += 1 else: gc_adim += 1 kalan[i].pop(0) ilerleyen += 1 tik += 1 ortusen += max(0, ilerleyen - 1) return tik, ortusen, islemci_adim, gc_adim def havuz(arka_uc, genislik): """Arayuz ayni, arka uc iki ayarla anlatilir: isci ve yuva. is parcacigi : genislik kadar isci, tek yuva (tek yorumlayici) surec : genislik kadar isci, genislik kadar yuva (ayri yorumlayici) """ return (genislik, 1) if arka_uc == "iş parçacığı" else (genislik, genislik) def kare(n): """Havuza verilen is: saf, yan etkisiz, sirasi onemsiz.""" return n * n YUKLER = (("girdi/çıktı ağırlıklı", 7), ("dengeli", 5), ("işlemci ağırlıklı", 1)) ARKA = ("iş parçacığı", "süreç") with ThreadPoolExecutor(max_workers=4) as havuz_nesnesi: print("aynı arayüz:", list(havuz_nesnesi.map(kare, range(6)))) print("map çıktısının sırası girişten gelir, çizelgeden değil") print() print("genişlik sekiz — aynı görev, iki arka uç") print(f"{'yük':<22s} " + " ".join(f"{ad:>22s}" for ad in ARKA)) print(f"{'':22s} " + " ".join(f"{'tik / örtüşen':>22s}" for _ in ARKA)) for ad, pay in YUKLER: g = gorevler(gc_payi=pay) hucre = [] for arka in ARKA: isci, yuva = havuz(arka, 8) tik, ortusen, _, _ = kosum(g, isci, yuva) hucre.append(f"{f'{tik} / {ortusen}':>22s}") print(f"{ad:<22s} " + " ".join(hucre)) for arka in ARKA: print() print(f"{arka} havuzu, genişlik arttıkça") print(f"{'genişlik':>9s} " + " ".join(f"{ad:>22s}" for ad, _ in YUKLER)) print(f"{'':9s} " + " ".join(f"{'tik / örtüşen':>22s}" for _ in YUKLER)) for genislik in (1, 2, 4, 8): hucre = [] for ad, pay in YUKLER: isci, yuva = havuz(arka, genislik) tik, ortusen, _, _ = kosum(gorevler(gc_payi=pay), isci, yuva) hucre.append(f"{f'{tik} / {ortusen}':>22s}") print(f"{genislik:9d} " + " ".join(hucre)) print() for arka in ARKA: sayi = {kosum(gorevler(gc_payi=pay), *havuz(arka, 8))[0] for _, pay in YUKLER} print(f"{arka} havuzu, üç yükün verdiği ayrı tik sayısı: {len(sayi)} " f"({sorted(sayi)})")
aynı arayüz: [0, 1, 4, 9, 16, 25]
map çıktısının sırası girişten gelir, çizelgeden değil
genişlik sekiz — aynı görev, iki arka uç
yük iş parçacığı süreç
tik / örtüşen tik / örtüşen
girdi/çıktı ağırlıklı 31 / 49 10 / 70
dengeli 42 / 38 10 / 70
işlemci ağırlıklı 73 / 7 10 / 70
iş parçacığı havuzu, genişlik arttıkça
genişlik girdi/çıktı ağırlıklı dengeli işlemci ağırlıklı
tik / örtüşen tik / örtüşen tik / örtüşen
1 80 / 0 80 / 0 80 / 0
2 44 / 36 52 / 28 73 / 7
4 34 / 46 44 / 36 73 / 7
8 31 / 49 42 / 38 73 / 7
süreç havuzu, genişlik arttıkça
genişlik girdi/çıktı ağırlıklı dengeli işlemci ağırlıklı
tik / örtüşen tik / örtüşen tik / örtüşen
1 80 / 0 80 / 0 80 / 0
2 40 / 40 40 / 40 40 / 40
4 20 / 60 20 / 60 20 / 60
8 10 / 70 10 / 70 10 / 70
iş parçacığı havuzu, üç yükün verdiği ayrı tik sayısı: 3 ([31, 42, 73])
süreç havuzu, üç yükün verdiği ayrı tik sayısı: 1 ([10])
Aynı Görev, İki Sonuç
İlk tablo dersin iddiasını üç satırda veriyor. Sol sütun ile sağ sütun arasındaki tek fark arka uç; görevler aynı nesne, genişlik aynı sayı, çizelgeleyici aynı işlev.
Girdi/çıktı ağırlıklı yükte iş parçacığı havuzu 31 / 49, süreç havuzu 10 / 70 veriyor. Dengeli yükte 42 / 38 ile 10 / 70. İşlemci ağırlıklı yükte 73 / 7 ile 10 / 70.
Sol sütun aşağı indikçe kötüleşiyor, sağ sütun hiç değişmiyor. Bunun kaynağı iki ayarın tanımında: iş parçacığı havuzu yükün işlemci payını görüyor, çünkü tek yuvada işlemci adımları sıraya giriyor. Süreç havuzunda yuva görev sayısına eşit olduğu için işlemci adımı kısıt olmaktan çıkıyor ve yükün bileşimi görünmez oluyor.
Buradan bu dersin cümlesi çıkıyor: aynı arayüzün arkasında aynı seçim yoktur. Bir kod tabanında havuz nesnesini değiştirmek bir satırlık iştir; ölçümde karşılığı ise yüke göre hiçbir şeyden yedi kat farka kadar değişir.
Genişlik İki Arka Uçta İki Ayrı Biçimde Kazandırır
Alt iki tablo genişliği birden sekize çıkarıyor ve iki apayrı biçim veriyor.
İş parçacığı havuzunda her sütun kendi eğrisini izliyor. Girdi/çıktı ağırlıklı sütun 80’den 44’e, 34’e, 31’e iniyor — azalarak kazanıyor. İşlemci ağırlıklı sütun ikinci genişlikte 73’e iniyor ve orada duruyor; dört ve sekiz genişlik hiçbir şey eklemiyor. Bu havuzda genişliği artırmanın karşılığı yükün girdi/çıktı payı kadardır.
Süreç havuzunda üç sütun da aynı: 80, 40, 20, 10. Genişlik iki katına çıktığında tik yarıya iniyor ve bu üç yükte de aynı. Sebebi işçi ile yuvanın birlikte büyümesi: her genişlikte canlı görevlerin hepsi ilerleyebiliyor ve kısıt yalnız görev başına düşen on adım.
İki tablonun ilk satırı da aynı: genişlik bir olduğunda iki arka uç da 80 / 0 veriyor. Havuzun arkasında ne olursa olsun, tek işçili havuz tek akıştır. Bir karşılaştırma havuz genişliğini yazmıyorsa okunamaz — ilk satır bunu gösteriyor.
Genişliği seçerken bakılacak sayı da bu tablolarda: iki ardışık satır arasındaki fark. İş parçacığı havuzunda işlemci ağırlıklı sütun için ikinci satırdan sonra fark 0; genişliği dörde ya da sekize çıkarmak ölçülebilir bir şey eklemiyor. Girdi/çıktı ağırlıklı sütunda aynı fark dörtten sekize 3 tik. Süreç havuzunda ise fark her adımda büyük ve üç sütunda da aynı. Genişlik için tek bir doğru sayı yok; olan şey, iki ölçüm arasındaki farkın küçüldüğü yerde durmak.
Bir uyarı bu tabloların dışında duruyor ve varsayımlarda yazılı: süreç sütunu veri devrini saymıyor. Üçüncü ders o bedeli ayrıca ölçmüştü; her göreve iki uçta on beşer aktarım adımı eklendiğinde dört yuvanın kazancı sıfırlanıyordu. Buradaki süreç sütunu bu yüzden ulaşılabilir en iyi sayıyı, yani bir üst sınırı veriyor. İş parçacığı sütununda böyle bir dış bedel yok, çünkü aynı adres alanında taşınacak bir şey de yok.
Havuza Verilen İşin Biçimi
Arayüzün aynı olması, verilebilecek işin de aynı olduğu anlamına gelmiyor ve seçim çoğu zaman sayılardan önce burada belirleniyor.
Süreç arka ucu, işin taşınabilir olmasını ister. Gönderilen işlev ve girdisi öbür uca aktarılabilir bir gösterime çevrilir, sonuç geri taşınır. Taşınamayan bir değer havuza giremez. Üstelik bu taşıma, üçüncü derste ölçülen aktarım adımlarının kendisidir: iş küçük ve veri büyük olduğunda kazanç orada erir.
İş parçacığı arka ucu taşıma istemez ama başka bir şey ister: işin kilidi bırakması. Bırakmayan bir iş, tabloların işlemci ağırlıklı sütununa düşer ve genişliği artırmak onu kurtarmaz — ikinci genişlikten sonra sütun kımıldamıyordu.
İkisi de işlerin bağımsız olmasını ister. Ölçümdeki sekiz görev birbirini beklemiyor; bir görevin sonucunu öbürü kullansaydı bekleyen görev o tikte ilerlemez ve örtüşen adım düşerdi. Havuz arayüzü bu bağımlılığı görmez, çünkü gönderilen her iş kendi başına gönderilir.
Ölçümdeki sayılar bu üç koşul sağlandığı için çıkıyor. Koşullardan biri bozulduğunda tablo aşağı doğru kayar; hiçbiri bozulmadığında yukarıdaki sayılar ulaşılabilir en iyi durumdur. Havuz seçerken sorulacak ilk soru bu yüzden genişlik değil: işin kendisi hangi arka ucun istediği biçimde mi?
Arayüzün Söylemediği
Son iki satır bu konunun kapanış sayısı: iş parçacığı havuzu üç yükte 3 ayrı tik sayısı veriyor ([31, 42, 73]); süreç havuzu 1 ([10]).
Bu iki sayı, arka uç seçiminin ne olduğunu tanımlıyor. İş parçacığı havuzuna geçmek, ölçümü yüke duyarlı kılmak demektir: hangi yükü verdiğinizi bilmeden sonucu kestiremezsiniz. Süreç havuzuna geçmek — devir bedeli sayılmadığı sürece — ölçümü yükten bağımsız kılar.
Arayüz ikisini de aynı biçimde çağırıyor ve bu bir kolaylık. Ama aynı çağrı biçimi, aynı davranışı vaat etmiyor. Ortak arayüzün gizlediği tam olarak bu dersin ölçtüğü şey: hangi adımın örtüşebildiği. Bir kod tabanında havuz nesnesini iki satırla değiştirip sonucun aynı kalmasını beklemek, bu iki sayıyı — 3 ile 1’i — aynı saymak demektir.
Bu bölümün bütün rejimleri şu ana kadar aynı soruyu yanıtladı: hangi rejim ne kazandırır? Beş ders boyunca yanıt hep aynı biçimde çıktı — kazanç, örtüşebilen adım sayısına ve o adımları kaç yuvaya dağıtabildiğinize bağlı. Yanıtın her seferinde bir girdisi vardı ve o girdi sorulmadı: hangi adımın hangi tür olduğu. Kurgu bunu bize verdi; gerçek bir programda kimse vermez.
Özet
- Yürütücü havuzu iş dağıtmanın ortak arayüzüdür; aynı çağrı biçimi iki arka uçla da kullanılır ve eşleme işleminin çıktı sırası girdiden gelir, çizelgeden değil.
- Modelde iş parçacığı arka ucu işçi = genişlik, yuva = 1; süreç arka ucu işçi = genişlik, yuva = genişlik demektir. Bütün fark bu iki ayardadır.
- Sekiz genişlikte aynı görev, iş parçacığı havuzunda üç yükte 31 / 49, 42 / 38 ve 73 / 7 verir; süreç havuzunda üçünde de 10 / 70.
- Genişliği artırmak iş parçacığı havuzunda yükün girdi/çıktı payı kadar kazandırır ve işlemci ağırlıklı yükte ikinci genişlikte durur; süreç havuzunda üç yükte de tik’i yarıya indirir.
- İş parçacığı havuzu üç yükte 3 ayrı tik sayısı verir, süreç havuzu 1. Aynı arayüzün arkasında aynı seçim yoktur; süreç sütunu ayrıca veri devrini saymayan bir üst sınırdır.
Sonraki Adım
Buraya kadar sayılan şey hep rejimdi: hangi rejim, hangi yükte, ne kazandırır. Her ölçümde görevlerin adım dizisi elimizin altındaydı — hangi adımın işlemci adımı, hangisinin girdi/çıktı adımı olduğunu kurgu söylüyordu. Gerçek bir programda o dizi yazılı değildir. Elde bir kod tabanı vardır ve hangi çağrının ağır olduğu, hangi satırın sıcak yolda durduğu görünmez. Sonraki ders bu boşluğu kapatır: bir programın hangi adımının ağır olduğu, süre ölçmeden, çağrı sayarak nasıl bulunur?
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.