Kapsamlı Bilgisayar Mühendisliği Eğitimi Rehberi 4 - İşlemcinin Anatomisi (Resimler Tekrar Yüklendi)

Öncelikle iyi bayramlar.

Aslında bu yazıda algoritmaları anlatacaktım fakat en düşük seviyede programlamanın nasıl bir şey olduğunu anlarsanız sonraki yazıyı daha rahat anlarsınız diye düşündüm, o yüzden 2. sınıfın 2. dönemine atlıyorum şimdi.

CS224 Computer Organization (Bilgisayar Mimarisi)

Bu derste işlemcinin int elmaSayısı = 10'u nasıl işlediği anlatılır özetle. 

Assembly Dili

Önce hoca bir giriş yapar, adam gibi bilgisayar mühendisi olmak istiyorsanız işlemcinin donanımın nasıl çalıştığını bileceksiniz der. Haklıdır.

Bilgisayarın tabanında 0 ve 1'ler vardır demiştik, aynı şekilde programlama dili de 0 ve 1'ler den oluşur hesapta. 0 ve 1'lerden oluşan dil en sade dildir, işlemci tarafından direkt okunabilir. Buna machine code denir. Halbuki siz java'da int elmaSayısı = 10; yazınca bilgisayarınızda yüklü java programı bunu machine code'a çevirip öyle okunur. Machine code bilgisayardan bilgisayara farklılık gösterir, o yüzden sizin bilgisayarınızda oluşturulan machine code'u başka bilgisayarda çalışmaz. Bu yüzden her bilgisayarın içinde java programı bulunmak zorundadır ki java dilinde yazılmış dosyalar çalışsın. O yüzden zırt pırt java kur uyarısı gelir.

int elmaSayısı = 10; aslında tek bir machine code ifadesine karşılık gelmemektedir. Birden fazla machine code ifadesi bulunur. Bu machine codeları kafadan okuyup yazmak zordur ve yapana da deli denir. Machine code ifadelerle birebir eşleşen dile assembly dili denir.

Bu derste MIPS isimli CPU'ya has dil öğretilir ama burada öğrendiklerinizi başka CPU'ların dilinde de kullanabilirsiniz. MIPS öğretilmesinin sebebi de dersin okutulduğu kitaptaki örneklerin MIPS'te yazılmış olmasıdır diyebiliriz. Önceki bilgiyi pekiştirirsek, MIPS işlemcili bir bilgisayarda MIPS kodu yazmakla aynı bilgisayarda java kodu yazmak aynı şeydir. Fark şu: MIPS yazarken kafayı yersiniz.

MIPS isimli işlemcinin içinde bir yerlerde 32 tane "Register" isimli hafıza birimi bulunur. Register'ın yapısını anlatmamıştım ama ne olduğunu söylemiştim. Yapısını soyutlayın gitsin, önemli değil. Bir register CPU'nun dizaynına göre bit tutar, 32 bit bilgisayarda 32 bit yani 32/8 = 4 byte (1 integer kadar) tutar. Yeni 64 bit bilgisayarlarda 8 byte tutar. 

MIPS isimli işlemcide 32 tane register vardır. Bunlar asıl bilgiyi tutmak için değil de geçici işlemler için kullanılır. Örneğin 1. register "0" sayısı için ayrılmıştır registerdır, ismi de zero'dur. $t0 - $t7 geçici değerler içindir. (t for temporary) Diğerlerinin de isimleri farklı farklıdır ve farklı görevler için kullanılır (fonksiyondan dönen registerı, fonksiyona verilen argümanlar için ayrı register vs. vs.)

(Asıl bilgiyi tutan hafıza birimleri Cache (İşlemcinin içindeki hafıza birimi), RAM ve harddisktir. İşlemcinin hangi sırayla bilgi çekeceğini sonra anlatacağım.)

Burada uzun uzun bu dili anlatmaya gerek yok. Merak eden MIPS'ı googlelasın.

Ben int elmaSayısı = 5; ifadesinin MIPS versiyonunu yazayım size.

Örnek buradan çalıntı

# da not işareti, yani okuyan insan için yazılmış. java'da bu // idi.

Not: Bilgisayarın en küçük işlem birimi byte demiştim hani, bir işlemcinin en küçük işlem birimine de word denir. İşlemciden işlemciye fark etmekle MIPS işlemcisinde 1 word 4 bytetır. Yani hafızadan en az bir word çekebilirsiniz, bir byte çekmek isterseniz yanında 3 byte bonus gelir. Bir tam sayının 4 byte olduğunu söylemiştik, e mantıklı o zaman.

li = load immediate = soldaki registera direkt bi sayı (immediate demişler buna) koy
sw = store word = soldaki registerın içindekilerini sağdaki değişkene ata ve hafızaya at.
Yukarıdakilere "instruction" denir.

example:
 .data                   # Bu "beyler burada değişken tanımlayacağım" demek.
elmaS: .word   # elmaS değişkenini (wordunu)tanımladım, içine önce 10 koydum.

 .text                   # bu da "beyler kodu yazıyorum sıkı durun." demek
__start: 
 li $t1, 5    #  $t1 = 5   ("load immediate")
 sw $t1, elmaS #  register $t1'dekileri elmaS'a at ve hafızaya depola yani :  var1 = $t1
 done               # assembly'im bitti
Burada uzun uzun diğer instructionları anlatacak halim yok tabii. Buradan çıkarmanız gereken iki sonuç var:

1- int elmaSayısı = 5 aslında o kadar basit değil.
2- tek satır olan iki kod aynı basitlikte değil.

Örneğin yukarıdaki örnekte önce bir registera 5 sayısını yükleyip ondan sonra o sayıyı hafıya atmak zorunda kaldık. İşlemci iki kez çalıştı. Bu mantıkla int elmaSayısınınKaresi = elmaSayısı * elmaSayısı yapsaydık önce elmaSayısını memoryden çekip $t1'e koyacaktık. Ondan sonra $t1 ile yine $t1'i çarpma instructionıyla çarpacaktık ve çıkan sonucu $t2'e koyacaktık. Buradan da bir işlem geldi. En son olarak $t2'yi elmaSayısınınKaresi değişkenine sw kullanarak atacaktık. Etti üç. Demek ki int ElmaSayısınınKaresi işlemi çok işlemci yakan bir işlemmiş.

Bu örnek kolaydı da, daha karmaşık kodlarda assembly okuyucu gibi düşünmek gerekiyor, işler karışıyor. Bu arada instruction ezberlemeye gerek yok çünkü bu dersin sınavları kitap açık yapılır.

Assembly dili özetle böyle bir şey. 

İşlemci Nasıl Çalışır?

Bu dil öğrenildikten sonra bu dildeki bir instructionın işlemcide nasıl çalışılacağı anlatılır.

Şöyle bir şeydir:


Durun! Hemen korkup ekranı kapatmayın! Parça parça anlatınca anlaşılacak.

Öncelikle şunu söyleyeyim, işlemci bir anda çalışmaz, adım adım çalışır. (MIPS işlemcisinin beş adımı var ama günümüzdeki işlemciler yirmi beş tane falan olabilir.) Her adımın süresi aynıdır ve bu süre de en yavaş adım tarafından belirlenir. Yani en yavaş adım atıyorum 50 pikosaniyede çalışıyorsa tüm adımlar 50 pikosaniyede çalışır ki en yavaş adım geride kalmasın. (1 saniye = 10^12 pikosaniye)

Yukarıda gördüğünüz yeşil şeritler register, o adım bitene kadar çıkan değerleri tutar. Yani atıyorum Insturction Fetch adımından 5 ve 10 değerleri çıktı, diğerleri de çalışmayı bitirene kadar o IF/ID registerı 5 ve 10'u tutar sonra bu değerleri sonraki adıma paslar.

Üstteki örnekteki li $t1, 5 instructionını kullanarak anlatayım. Yani işlemci $t1 registerına 5 koyacak.

Intruction Fetch:

En soldaki "Address" registerında sıradaki instructionın (bu durumda li $t1, 5'in) adresi bulunur. Bu address Instruction Memory dediğimiz kodumuz yer aldığı hafızaya girer, Memory adresi okur ve o adreste tutulan instructionı sıradaki adıma gönderir. Bu arada adres "Adder" yardımıyla artırılır ki işlemci bir daha çalıştığında sıradaki adres memory'e yüklensin böylece sıradaki instruction işlensin. (Gereksiz ek bilgi: bazı durumlarda, örneğin if-else ve looplarda, işlemcinin hemen altındaki satırın değil başka bir satırı işlemesi gerekir. En soldaki "Adder"dan çıkan kabloyu takip ederseniz göreceksiniz ki bir MUX'a bağlanıyor. MUX seçiyor sıradaki işlemi mi okutayım başka işlemimi. Olay bu.)

Instruction Decode, Registration Fetch:

Memoryden li, $t1 ve 5 bilgileri çıktı. $t1 registerın adresi yani kaçıncı register olduğu, bu registerlar derneğine gidiyor yani registerlara "Ben $t1 adresindeki registera yazı yazacam ha!" bilgisi gidiyor. Aynı zamanda 5 sayısı imm olarak çıkıyor. Burada bir sıkıntı var o da şu, 5 sayısı direkt olarak instructionın içinde. Yani instruction aslında 1 word = 4 byte = 32 bitten oluşuyor ya, bunun 5 tanesinde $t1 adresi tutuluyor (Çünkü 32 register var, 2^5 = 32) atıyorum 16'sında ise 5 sayısı tutuluyor. Halbuki bir tam sayı = 4 byte = 32 bit. Yukarıdaki "SignExtend" de bu sayıyı 32 bite çeviriyor.

Yukarı baka baka şaşı olmayın diye resmi tekrar koyuyorum:



Execute

Burada normalde toplama çıkarma yapacaktı ALU abi ama toplama çıkarma yapacak bir şey yok. ALU'ya 5 sayısı girer ve aynen çıkar. $t1'dekiyle 5 toplasaydık ikisini ALU'ya sokar ALU'ya da toplama yap derdik.

(Aradaki Zero? neydi unuttum)

Memory

Burada da hafızayla ilgili işleri yapıyor. Hafızaya koyacaksa "Memory"e adres ve değer giriyor. Yok hafızadan çekecekse adresi giriyor, Memory'den de değer çıkıyor. 

Writeback:

Duruma göre hafızadan çıkan değer registerlara geri yazılıyor. WB Data'dan çıkan oku takip edin.

İşlemci özetle böyle çalışıyor. Tabii her şeyi söylemedim, yoksa bu yazı uçar gider. Ama şu an bildikleriniz yanlış değil. Ve işlemci hakkında genel kültür edinmiş oldunuz.

Pipelining

Pipeline boru hattı demek. Bildiğiniz gibi boruda su küp küp akmaz!! Su akarken arkadan da su gelir, ta ki akacak su kalmayana kadar!!! Yukarıda da aynı mantık. Sadece bir tane instructionın sırayla Fetch, Decode vs. diye beş adımdan geçmesini bekleyip sonra en son yeni instructionı gönderirsek hapı yutarız.

Onun yerine ilk gönderdiğimiz instruction Fetch'ten Decode'a geçtiğimiz anda yani instructionı göndeririz.

Bir adım 100 pikosaniye olsa, tek tek gönderdiğimizde bir instruction için 5*100 = 500 pikosaniye harcarız. Bu on instruction için 5000 pikosaniye eder.

Halbuki on instructionda ilk instruction beşinci adıma geldiğinde tüm adımlar çalışır hale gelir. İşlemci toplam 14 kere aktif olur, 14*100 = 1400 pikosaniyede biter iş. 1400 <<<< 5000 pikosaniye. Kod bir anda 3.5 kat hızlandı. 

Fakat bu da sıkıntılar çıkarır. Sonradan gelen instruction, önceki instructionın registerına salça olabilir. Veya birinci instruction $t1 registerına yazarken ikinci instruction $t1 registerına okuyorsa, birinci instruction daha $t1'e yazamadan öbürü okumuş olur, sıkıntı çıkar. Bu tip sıkıntılara çeşitli çözümler var, derste de genel olarak bunlar anlatılır. En basit örnek şu: bilgisayarınızdaki java programı (derleyici denir buna aslında) java kodunu 1'lere 0'lara dökerken bu örnekteki gibi aynı register peşpeşe yazılıp okunuyorsa önce kodların arasında başka kodlar sokuşturmaya çalışır, beceremezse araya "nop" diye boş kodlar koyup işlemciyi oyalar. 

Hafıza

Üç tip hafıza tipi var diyebiliriz. Birincisi cache, işlemci en yakın olanı dolayısıyla en hızlı çalışanıdır. Oldukça küçüktür, fazla bir şey depolayamaz. Çok pahalıdır. RAM Cache'dan yavaştır ama yine de hızlıdır. Elektrik gidince içindeki bilgiler de gider. Harddisk içlerinden en yavaşıdır. Harddisk adı üzerinde disktir, dönerek çalışır, bilgisayardan vızzzzz sesini duyarsanız bilin ki harddisk çalışıyordur. Dolayısıyla çok da yavaştır. Dünyaları içine alabilir.

İşlemci hafızadan bir şey çekeceği zaman önce cache'e bakar. Cache'de yoksa (buna cache miss) denir anlar ki iş uzun sürecek. O ara memory'i kapatır başka instructionlara, yani aynı anda farklı instructionlar için bir şeyler çekemez. Yine bulamazsa harddiske gider.

Bilgilerin cache'te nasıl depolanacağı önemlidir çünkü çok vakit kazandırır. Bilgiler cache'ten tane tane değil bloklar halinde çekilir. Örneğin x marka cache'tan bir word (4 byte) bilgi çekilirse ondan hemen sonra gelen 3 word (12 byte) da peşinden gelir. Atıyorum bir dizinin ilk dört elemanını kullanan bir kod yazdık, cache'in bu özelliği sayesinde dizi[0] diyip ilk elemanı hafızadan istediğimiz anda diğer üç elemanı da istemiş oluruz ve zaman kazanırız.

Cache'te yer sınırlı olduğundan belli bir kurala göre cache kendi içeriğini çeker. En son kullanılanı sil veya en başta kullanılıp bir daha el sürülmeyenleri sil gibi kurallar vardır. Cache'in hangi kuralda çalıştığını bilip ona göre kod yazmak önemlidir. Örneğin atıyorum cache'imiz sadece 3 dizi/array elemanını depolayabiliyor, sonra en eski kullanılandan itibaren başlıyor. 

Bu durumda şu kodu yazmak büyük mallık olur:

int elmalar = new int[4];
int döngüSayısı = 100; // yüz kere dönsün
int gereksizSayı = 0;

while (döngüSayısı > 0) {
   gereksizSayı = elmalar[0];
   gereksizSayı = elmalar[1];
   gereksizSayı = elmalar[2];
   gereksizSayı = elmalar[3];

}

Bu durumda şu olacak, cache'e önce elmalar[0], sonra elmalar[1], sonra elmalar[2] depolanacak. Sonra cache'te yer kalmadığı için cache elmalar[0]'ı silip yerine elmalar[3]'ü yazacak. Ardından elmalar[0] için cache'e bakacak, baktı yok, elmalar[1]'i silip yerine elmalar[0]'ı yazacak. Bu böyle gidecek. Hiçbir zaman cache'i kullanamayacağız. 

Hafıza kısmında özet olarak bu ve bunun gibi mühendislik problemleri anlatılır. Çeşitli cache tipleri verilir ve verilen kodda cache'e ne olur o sorulur, sıkıntıları gidermek için çözüm istenir.

I/O Cihazları

Bu kısım "İşlemcinin her bir şeyini anlattık, bari bunu da anlatalım." diye anlatılır, mühendislik yoktur, tamamen ezberdir ve sıkıcıdır. Tam da havaların ısındığı zamana denk geldiği için pek dinlenemez de. Ben kitaptaki örneği değiştirip yazmıştım. Anlatmaya gerek yok, unuttum zaten. Tek hatırladığım, mouse takılan yere karşılık gelen bir değişken var, onun sayılarını falan değiştiriyoruz.

Tebrikler! Artık işlemci ne biliyorsunuz! 

Bu derste de haftalık lablar olur ve öğrencilerin anasını ağlatır. MIPS'le java kodu, Verilogla pipeline yazmak oldukça zordur. Ayrıca FPGA ile tetris yazmak gibi zor projeleri vardır. Ben aldığımda ders partnerliydi, partnerim çok zekiydi ve verilogları takır takır yapıyordu, ben MIPS varsa yapıyordum yoksa yatıyordum çünkü önceki dönem partnerim tüm labları arkadaşına yaptırdığı için Verilog öğrenememiştim. Sonra bireysel oldu iyice zorlaştı. Alacaklara geçmiş olsun.

2 yorum

Computer Architecture electiveleri genelde çıkıyor mu? Ders geçmişinde nadir açıldığını gördüm de ondan. :/

Reply

Hangi electiveler onlar? Benim bildiğim Parallel Computing ve M2M Systems (Internet of Things) var sadece düzenli açılan. Bunlar yeter zaten.

Reply

Yorum Gönder