Bölüm 6:Saat, Çekirdek ve Mantıksal İşlemci
CPU'ya “Sen”i Katmak kitabının parçası: bilgisayarının programları nasıl çalıştırdığına doğru inen uzun bir teknik tavşan deliği.
Tüm bölümler
- Giriş
- Sayılar: Bit'ten Kayan Noktaya
- Metin, Bellek ve Kod
- Temeller
- Kernel, User Mode ve Syscall
- Mimariler: x86, ARM ve Diğerleri
- Saat, Çekirdek ve Mantıksal İşlemci
- Bellek Hiyerarşisi
- Cache Nasıl Çalışır
- Zamanı Dilimle
- Aynı Anda İki Şey
- İşlemciyi Hızlandıran Hileler
- Tahmin, Spekülasyon ve Spectre
- Bir Program Nasıl Çalıştırılır?
- Shell'den Kernel'e
- Bir ELF Ustasına Dönüşmek
- Bellek Aslında Sanal
- Adres Çevirisi ve TLB
- Bellek Güvenliği ve Sertleştirme
- Fork'lar ve COW'lar Hakkında Konuşalım
- Dosya Sistemi ve I/O
- Sanal Makineler ve Container'lar
- Son Söz
Bir önceki bölümde talimat kümesini bir sözleşme olarak anlattım: makine kodunun ne anlama geldiğini söyleyen, üreticiden üreticiye değişmeyen kısım. Sözleşmenin ne olduğunu biliyoruz artık. Sıra onu yerine getiren fiziksel makinede.
O makine hakkında şimdiye kadar iki şeyi sessizce varsaydım. Birincisi, işlemcinin bir ritmi olduğu: 3. bölümde register erişiminin “bir clock cycle”, RAM erişiminin “yüzlerce” sürdüğünü söylemiştim ama çevrimin ne olduğunu tanımlamadım. İkincisi, ortada tek bir işlemci olduğu — oysa elindeki makinede muhtemelen sekiz tane var ve görev yöneticisi on altı gösteriyor.
Bu bölüm o iki varsayımı açıyor. İkisi de aynı hikâyenin parçası, çünkü işlemcinin neden daha fazla çekirdeğe döndüğünün cevabı, saatin neden hızlanmayı bıraktığında saklı.
Bu bölümde neyi çözüyoruz?
- Bir clock cycle’ın ne olduğunu ve süresini neyin belirlediğini göreceğiz.
- Yirmi yıldır GHz sayısının neden yerinde saydığını, buna karşılık işlemcilerin nasıl hızlandığını çözeceğiz.
- Turbo ile throttling’in aynı bütçenin iki yüzü olduğunu göstereceğiz.
- Fiziksel çekirdek ile mantıksal işlemci arasındaki farkı netleştireceğiz.
Saat: Makinenin Ortak Ritmi
İşlemcinin içi, birbirine bağlı milyarlarca transistörden oluşan bir devre. Bu devrede bir toplama işleminin sonucu anında ortaya çıkmaz; elektrik sinyalinin kapılardan geçmesi, gerilimlerin oturması zaman alır. Asıl sorun bu sürenin her yol için farklı olması. Bir register’dan diğerine değer taşımak kısa bir yol, iki 64 bitlik sayıyı toplamak daha uzun, çarpmak çok daha uzundur.
Devrenin bir köşesinde hesap çoktan bitmişken başka bir köşesinde sinyaller henüz oturmamış olabilir. O ara anda devreye bakarsan ortada bir sonuç yoktur; yarısı eski, yarısı yeni bir karışım vardır. O karışımı okuyan bir sonraki aşama saçmalar.
Çözüm, herkese ortak bir “şimdi oku” komutu vermek. İşlemcinin içinde clock signal denen, saniyede milyarlarca kez yükselip alçalan kare dalga bir gerilim dolaşır. Devredeki depolama elemanları yalnızca bu sinyalin kenarında değer yakalar. Aradaki süre boyunca sinyaller istedikleri kadar dalgalansın, kimse bakmıyordur; kenar geldiğinde ne varsa o kaydedilir.
İki kenar arasındaki süreye clock cycle denir. Bu kitapta “dört clock cycle” gibi ifadeler gördüğün her yerde kastedilen şey budur: mutlak bir süre değil, makinenin kendi ritmindeki dört vuruş. Saniyede kaç vuruş olduğu ise frekanstır ve GHz ile ölçülür; 3 GHz, saniyede üç milyar vuruş demektir.
Buradan doğrudan bir sonuç çıkıyor ve işlemci tasarımının tamamı bunun üzerine kurulu. Çevrim süresi, o çevrimde tamamlanması gereken en yavaş yolun süresinden kısa olamaz. Devrede tek bir uzun yol varsa bütün makine onun hızına mahkûmdur. Tasarımcının işi bu yüzden ortalamayı iyileştirmek değil, en uzun yolu kısaltmaktır; o yola critical path denir.
Bir Çevrimde Ne Sığar?
“Bir talimat bir çevrim sürer” hoş bir basitleştirme ama doğru değil. Çevrim, işin bittiği an değil, işin ölçüldüğü birimdir.
Bir register’dan diğerine değer kopyalamak tek çevrimde biter. Toplama da öyle. Çarpma birkaç çevrim ister, bölme onlarca. Bir sonraki bölümde çıkaracağımız bellek piramidinin tamamı da bu birimle ölçülecek: en yakın cache katmanına uzanmak birkaç çevrim, RAM’e uzanmak birkaç yüz. Ve o birkaç yüz çevrim, işlemcinin gerçekten oturup beklediği süredir.
Bu yüzden saat hızını ikiye katlamak programı ikiye katlamaz. RAM’i bekleyen bir çekirdek, saat iki kat hızlıyken aynı veriyi beklemek için iki kat fazla çevrim harcar. Beklemenin nanosaniye cinsinden süresi değişmez; yalnızca boşa geçen vuruş sayısı artar. Hızlandırdığın şey, zaten beklemekte olan bir makineyi daha hızlı bekletmektir.
GHz Neden Yirmi Yıldır Yerinde Sayıyor?
2004’te bir masaüstü işlemcisi 3 GHz’de çalışıyordu. Bugün, yirmi yıl sonra, hâlâ 3-6 GHz aralığındayız ve en iddialı masaüstü çipleri bile 6 GHz’i ancak turbo’da, kısa süreler için geçiyor. Oysa aynı sürede transistör sayısı yüzlerce kat arttı. Neden saat hızı artmadı?
Cevap, adını Robert Dennard’dan alan bir kuralın çökmesi. Dennard ölçekleme, transistörler küçüldükçe güç yoğunluğunun sabit kaldığını söylüyordu: daha küçük transistör daha az voltaj ister, daha az voltaj daha az ısı demektir, yani hızı artırmak bedavaya yakındır. Yaklaşık 2005’e kadar sektör bu bedavaya yakın hızlanmanın üzerinde yaşadı — yazılımcılar hiçbir şey yapmadan, sadece bekleyerek programlarının hızlandığını gördü.
Sonra fizik araya girdi. Transistörler o kadar küçüldü ki sızıntı akımı baskın hâle geldi; kapalı transistör bile akım harcamaya başladı. Voltajı daha fazla düşürmek mümkün olmayınca, saat hızını artırmanın bedeli doğrudan ısıya dönüştü.
Duvarın şekli şu: bir devrenin harcadığı güç frekansla doğru orantılı, ama voltajın karesiyle orantılıdır. Ve daha yüksek frekansta çalışmak daha yüksek voltaj ister. Yani frekansı biraz artırmak için voltajı biraz artırırsın, güç ise ikisinin çarpımından çok daha hızlı büyür: voltaj frekansla birlikte yükseldiği için güç kabaca frekansın küpüyle artar. Yüzde on fazla hız, yüzde otuza yakın fazla güç demektir. Son yüzde onluk hız bu yüzden orantısız bir ısı faturası çıkarır.
Sektörün cevabı yön değiştirmek oldu ve iki koldan ilerledi. Tek çekirdeği hızlandıramıyorsan yan yana koyarsın; bu bölümün ikinci yarısı bunun hikâyesi. Ya da aynı saat hızında çevrim başına daha fazla talimat bitirirsin; onu da pipeline ve superscalar yürütmeyi anlatacağım 11. bölüme bırakıyorum.
Aynı Saatte Daha Çok İş: IPC
Frekans durdu ama performans durmadı. Çünkü bir işlemcinin ne kadar sürede iş bitirdiğini belirleyen tek çarpan frekans değil.
Bir programın çalışma süresi üç şeyin çarpımıdır: kaç talimat yürütüldüğü, talimat başına kaç çevrim harcandığı ve bir çevrimin ne kadar sürdüğü. Frekans yalnızca sonuncusunu değiştirir. Ortadaki çarpana — çevrim başına bitirilen talimat sayısına — IPC (Instructions Per Cycle) denir ve son yirmi yılın kazancı büyük ölçüde buradan geldi.
Bunun pratik sonucu şu: 3 GHz’lik yeni bir işlemci, 4 GHz’lik eski bir işlemciyi rahatlıkla geçebilir. Yeni olan her vuruşta daha fazla iş bitiriyorsa toplamda öne geçer. Bu yüzden iki farklı nesli GHz sayısına bakarak karşılaştırmak yanıltıcıdır; sayı, ancak aynı mimari ailesi içinde ve kaba bir gösterge olarak işe yarar.
Aynı denklem, bir önceki bölümde konuştuğumuz mimari farklarının performans karşılaştırmasını neden zorlaştırdığını da açıklıyor. Talimat sayısı ISA’ya bağlıdır: bir mimaride tek talimatla yapılan iş, başka bir mimaride üç talimat gerektirebilir. Üç çarpandan ikisi birden değişmişken yalnızca frekansı karşılaştırmanın anlamı kalmaz.
Turbo ve Throttling: Aynı Bütçenin İki Yüzü
Modern bir işlemcinin tek bir saat hızı yoktur. Frekans, saniyede yüzlerce kez değişir.
Sebebi güç ve ısı bütçesi. Üretici çipe bir güç zarfı biçer; çip o zarfın içinde kaldığı sürece istediği frekansta çalışabilir. Boştayken frekansı düşürüp elektrik harcamamak mantıklıdır. Tek bir çekirdek çalışıyor ve diğerleri boştaysa, bütün bütçeyi o çekirdeğe verip onu ilan edilen taban hızın üzerine çıkarmak da mantıklıdır. İkincisine turbo denir.
Buradaki incelik şu: turbo bir ödül değil, taban hızın dürüst tanımıdır. Üreticinin ilan ettiği taban frekans, bütün çekirdekler tam yükte çalışırken garanti edilen hızdır. Turbo frekansı ise ancak koşullar elverdiğinde görülür. Yani “5,2 GHz’e çıkar” ifadesi tek çekirdek, iyi soğutma ve kısa süre demektir; sekiz çekirdeğin sekizini birden aylarca 5,2 GHz’de çalıştırmak diye bir şey yoktur.
Madalyonun diğer yüzü throttling. Çip ısınır ya da güç sınırına dayanırsa frekansı düşürür. Bu bir arıza değil, tasarımın kendisidir; alternatifi çipin zarar görmesidir. Ama gözlemlenebilir sonucu can sıkıcıdır: aynı programı iki kez çalıştırırsın, ikincisi makine ısındığı için daha yavaş biter.
Bu, ölçüm yapan herkesin başına gelen bir tuzaktır. Bir kod parçasını hızlandırdığını sanıp aslında yalnızca ilk koşuyu soğuk makinede yapmış olabilirsin. Ölçümü tekrarlamak ve tek bir sayı yerine dağılıma bakmak bu yüzden daha güvenilirdir.
DerinleşmeClock signal her yere aynı anda varmaz
Buraya kadar saati, bütün çipe aynı anda ulaşan ortak bir vuruş gibi anlattım. Fiziksel olarak öyle değildir.
Clock signal da sonuçta bir elektrik sinyalidir ve çipin bir ucundan diğerine yayılması zaman alır. Kaynağa yakın bir devre kenarı erken görür, uzak bir devre geç. Aradaki farka clock skew denir. Fark yeterince büyürse, erken uyanan devre henüz hazırlanmamış bir değeri yakalar ve makine sessizce yanlış hesaplar.
Tasarımcılar bunu özel olarak dengelenmiş dağıtım ağlarıyla çözer. En bilinen düzen, dallanma noktalarının hepsinde yolların eşit uzunlukta tutulduğu ve bu yüzden çipin fotoğrafında büyük bir “H” harfi gibi görünen H-tree dağıtımıdır. Amaç sinyali hızlı taşımak değil, her yere eşit gecikmeyle taşımaktır.
Bu ağın bedeli küçük değildir. Saat ağı çipin en çok anahtarlanan devresidir — her vuruşta baştan sona yön değiştirir — ve modern bir işlemcide toplam gücün kayda değer bir kısmını tek başına harcar. İşlemcilerin kullanılmayan bölümlerin saatini tamamen kesmesinin (clock gating) sebebi budur: hesap yapmayan bir birimin bile saat aldığı sürece elektrik harcamasını engellemek.
Çekirdek: Aynı Çipte Kaç Makine Var?
Şimdi ikinci varsayıma geçelim. Frekans duvara çarpınca sektörün seçtiği yol, tek çekirdeği hızlandırmak yerine aynı çipin üzerine birden fazla bağımsız çekirdek koymaktı.
Çekirdek (core), kendi başına talimat yürütebilen eksiksiz bir işlemcidir. Kendi register setine, kendi instruction pointer’ına, kendi hesap birimlerine ve kendi özel cache katmanlarına sahiptir. Bir çekirdeğin ne yaptığı diğerini ilgilendirmez; ikisi ayrı programları ayrı hızlarda yürütür.
Terimler burada birbirine karışmaya müsait, o yüzden üçünü ayıralım. Yonga (die) silikonun tek bir parçasıdır; çekirdekler onun üzerinde durur. Soket, anakartta çipin oturduğu fiziksel yuvadır; sunucularda aynı anakartta iki ya da dört soket bulunabilir. Modern işlemcilerin bir kısmı ise tek bir büyük yonga yerine, aynı paketin içine yerleştirilmiş birden fazla küçük yongadan oluşur; bunlara chiplet denir. Küçük yonga üretmek daha az fire verdiği için bu düzen maliyeti düşürür, ama iki chiplet arasındaki iletişim aynı yonga içindeki iletişimden yavaştır.
Çekirdeklerin paylaştığı bir de ortak cache katmanı vardır ve orası aynı zamanda buluşma noktalarıdır. Buradan doğal olarak bir soru çıkıyor: iki çekirdek aynı veriye dokunursa ne olur? İkisi de o veriyi kendi özel cache’ine kopyalar; biri değeri değiştirdiğinde diğerinin elinde bayat bir kopya kalır. Donanımın bunu engellemek için yürüttüğü sessiz pazarlığın adı cache coherence ve iki bölüm sonra, cache’in içini açtığımızda ayrıntısını konuşacağız. Şimdilik akılda kalması gereken tek şey, bunun bedava olmadığı.
Her Çekirdek Aynı Değil
Uzun süre bir işlemcideki bütün çekirdekler birbirinin kopyasıydı. Artık her zaman öyle değil.
Telefon tarafında yıllardır kullanılan, masaüstünde ise son yıllarda yaygınlaşan düzende çekirdekler iki sınıfa ayrılır. Büyük çekirdekler tek thread performansı için tasarlanır: geniş, karmaşık, hızlı ve güç açısından pahalıdır. Küçük çekirdekler verim için tasarlanır; daha yavaştır ama aynı alana daha çoğu sığar ve çok daha az harcar. Arka planda çalışan bir senkronizasyon işini küçük çekirdeğe vermek, büyük çekirdeği uyandırmaktan kat kat ucuzdur.
Bu düzen işletim sistemine yeni bir sorumluluk yükler: hangi işin hangi sınıfa gideceğine karar vermek. 9. bölümde tanıyacağımız scheduler, artık yalnızca “sıra kimde” sorusunu değil, “bu iş hangi tür çekirdeğe yakışır” sorusunu da cevaplamak zorunda. Yanlış karar gözle görülür: interaktif bir uygulamanın küçük çekirdekte takılması, kullanıcının doğrudan hissettiği bir gecikmedir.
Mantıksal İşlemci: Görev Yöneticisi Neden İki Katını Gösteriyor?
Sekiz çekirdekli bir makinede görev yöneticisini açtığında on altı kutucuk görürsün. İkinci sekizi nereden geldi?
Cevap SMT (Simultaneous Multithreading); Intel’in ticari adıyla Hyper-Threading. Fikri anlamak için çekirdeği ikiye ayırmak gerekiyor. Hesabı yapan birimler — toplama devreleri, çarpma devreleri, cache — tek takımdır ve paylaşılır. Buna karşılık durum bilgisi, yani register seti ve instruction pointer, çekirdeğin içinde ikişer kopya hâlinde tutulur.
Sonuç şu: tek bir fiziksel çekirdek, iki bağımsız yürütme akışını aynı anda içinde barındırabilir. İşletim sistemi bu akışların her birini ayrı bir işlemci olarak görür ve onlara mantıksal işlemci (logical processor) denir. Sekiz fiziksel çekirdek, on altı mantıksal işlemci.
- Tek takım, iki durum. Çekirdekte hesabı yapan birimler tek takımdır. Register seti ve instruction pointer ise iki kopya hâlinde tutulur; iki akış birbirinin defterine bakmaz.
- A akışı çalışıyor. İlk iki çevrimde A hesap yapıyor ve birimler dolu. Bu noktaya kadar SMT açık ile kapalı arasında hiçbir fark yok.
- A bellekte takıldı. A istediği veriyi bulamayıp RAM'i beklemeye başlıyor. SMT olmasaydı hesap birimleri bu çevrimler boyunca boş oturacaktı.
- B boşluğu dolduruyor. İkinci durum kopyası hazır beklediği için çekirdek, sıfırdan kurulum yapmadan B'nin işine geçiyor ve boş çevrimleri değerlendiriyor.
- Kazanç boş anlardan. Birimler daha dolu geçti ama iki katı iş çıkmadı. SMT ikinci bir çekirdek vermez; birincinin bekleme anlarını satar.
Kazanç boş kalan birimlerden geliyor. Bir akış bellekten veri beklerken o pahalı hesap birimleri yüzlerce çevrim boyunca boş oturacaktı. SMT o boşluğu ikinci akışın işiyle doldurur.
Buradan çıkan en önemli sonucu net söyleyeyim: SMT sana ikinci bir çekirdek vermez, birincinin boş anlarını satar. On altı mantıksal işlemci, sekiz çekirdeğin iki katı hesap gücü demek değildir. Tipik kazanç, iş yüküne göre yüzde on ile otuz arasındadır.
Ne zaman işe yaradığı da bu mantıktan çıkar. İki akış farklı türde iş yapıyorsa — biri bellek bekliyor, diğeri hesap yapıyor — birbirlerinin boşluklarını doldururlar ve kazanç büyür. İkisi de aynı birimi doyurmaya çalışıyorsa, örneğin ikisi de yoğun kayan nokta hesabı yapıyorsa, sıraya girerler ve kazanç erir. Bazı durumlarda zarar bile eder: iki akış aynı özel cache’i paylaştığı için birbirlerinin verisini kovabilir.
Bunun doğrudan pratik bir sonucu var. Bir hesaplama işini paralelleştirirken thread sayısını mantıksal işlemci sayısına eşitlemek çoğu zaman doğru cevap değildir; yoğun hesap yapan kodda fiziksel çekirdek sayısı genellikle daha iyi sonuç verir. Ölçmeden karar verilecek bir konu değil.
Güvenlik tarafında da bir bedeli var. İki mantıksal işlemci aynı fiziksel kaynakları paylaştığı için, birinin davranışı diğerinin gözlemleyebileceği izler bırakır. 12. bölümde spekülatif yürütmeyi ve yan kanal saldırılarını konuşurken bu paylaşımın ne anlama geldiğini göreceğiz; bazı bulut sağlayıcılarının SMT’yi tamamen kapatmasının sebebi budur.
DerinleşmeAmdahl mı Gustafson mu: daha çok çekirdek ne kadar işe yarar?
Çekirdek eklemek ilk bakışta sınırsız görünür. Değildir ve sınırı 1967’de Gene Amdahl koydu.
Amdahl yasası şunu söyler: bir programın ancak paralelleşebilen kısmı hızlanır; seri kalan kısım hızlanmaz ve toplam hızlanmayı o belirler. Formülü basit — programın %5’i seri ise, sonsuz çekirdekle bile en fazla 20 kat hızlanabilirsin. %1 seri ise tavan 100 kattır. Çekirdek eklemek bu tavanı yükseltmez, yalnızca ona yaklaştırır.
Sayılarla: %95 paralel bir programda 16 çekirdek yaklaşık 9,1 kat hızlanma verir. 64 çekirdek 15,4 kat. Yani çekirdeği dört katına çıkarmak hızlanmayı %70 artırır. 1024 çekirdek ise 19,6 katta durur; artık çekirdek eklemek neredeyse hiçbir şey getirmez.
Bu, 1980’lerde paralel hesaplamaya karamsar bakılmasının sebebiydi. Sonra 1988’de John Gustafson karşı bir argüman getirdi: Amdahl yanlış soruyu soruyor.
Gustafson’ın gözlemi şu: pratikte insanlar aynı problemi daha hızlı çözmek için makine büyütmez; daha büyük problem çözmek için büyütür. Hava tahmini yapan biri, aynı çözünürlüklü tahmini daha hızlı almak istemez; aynı sürede daha yüksek çözünürlüklü tahmin ister. Problem büyüdükçe seri kısım (kurulum, toplama, çıktı) sabit kalırken paralel kısım büyür, yani seri oran düşer.
İkisi çelişmez; farklı soruları cevaplar. Amdahl: problem sabitken çekirdek eklemenin getirisi nedir? Gustafson: çekirdek sayısı arttıkça ne kadar büyük problem çözebilirim?
Pratik sonuç ikisinin ortasındadır ve bugün hâlâ geçerlidir: sabit boyutlu bir işi paralelleştirerek çok az kazanırsın, o yüzden ölçeklenme hikâyesinin gerçekten ölçeklenen kısmı neredeyse her zaman veri tarafındadır. Sunucu dünyasında çekirdeklerin işe yaramasının sebebi tek bir isteği hızlandırmaları değil, aynı anda binlerce bağımsız istek işlemeleridir.
Özet
Peki, ne öğrendik?
- Clock signal devrenin her yerine ortak bir “şimdi oku” komutu dağıtır; iki kenar arasındaki süre bir clock cycle’dır, saniyedeki vuruş sayısı ise frekans.
- Çevrim süresini o çevrimdeki en yavaş yol belirler; makinenin hızı ortalamaya değil critical path’e bağlıdır.
- Dennard ölçeklemenin çökmesiyle frekans yirmi yıldır yerinde sayıyor; güç voltajın karesiyle büyüdüğü için son yüzde onluk hız orantısız bir ısı faturası çıkarır.
- Performans üç çarpandan oluşur: talimat sayısı, talimat başına çevrim ve çevrim süresi. Frekans yalnızca sonuncusudur; son yirmi yılın kazancı IPC’den geldi.
- Turbo taban hızın dürüst tanımından doğar, throttling aynı bütçenin diğer yüzüdür. İkisi birlikte, aynı kodun iki koşusunun neden farklı sürdüğünü açıklar.
- Çekirdek eksiksiz bir işlemcidir; kendi register’ları ve özel cache’i vardır, ortak katmanı paylaşır. Yonga, soket ve chiplet birbirinden farklı şeylerdir.
- Mantıksal işlemci, aynı fiziksel çekirdeğin içindeki ikinci durum kopyasıdır. SMT ikinci bir çekirdek vermez; birincinin boş anlarını satar.
Bu bölümde bir çekirdeğin RAM’i beklerken yüzlerce çevrim boşa harcadığını iki kez söyledim ve iki kez geçiştirdim. Artık geçiştiremem, çünkü bir sonraki bölümün tamamı o bekleme üzerine kurulu.
7. bölüme devam et: Bellek HiyerarşisiAynı silikon parçasının üzerindeki register’a uzanmak bir çevrim sürüyorsa, birkaç santim ötedeki RAM’e uzanmak neden yüzlerce çevrim sürüyor — ve mühendisler bu uçurumu kapatmak için ne yaptı?