- Bir von Neumann bilgisayarının parçalarını ve getir–çöz–yürüt döngüsünü açıklamak
- İşlemci süresini ve ortalama bellek erişim süresini hesaplamak
- Tam sayıları ikiye tümleyende yazmak ve taşmayı tanımak
- Bir sayıyı IEEE 754 biçiminde kodlamak ve yuvarlama hatalarını açıklamak
Telefonundaki 3 GHz'lik işlemci saniyede 3 milyar tik atar, ama Python'da 0.1 + 0.2 == 0.3 yazarsan cevap False olur. İki gerçek de bilgisayarın nasıl kurulduğundan kaynaklanır: işlemci komutları tek tek getirip yürütür ve sayıları sonlu sayıda bitte saklar. Bu derste donanımın programcıyı en çok etkileyen parçalarını inceleyeceğiz.
Von Neumann mimarisi ve komut döngüsü
Program ve veriler aynı bellekte tutulur; işlemci (aritmetik mantık birimi, denetim birimi ve yazmaçlar) komutları bellekten sırayla getirip yürütür; hepsi bir yolla (bus) bağlıdır. Programı değiştirmek için kabloları değil, belleğin içeriğini değiştirmek yeter.
| Yazmaç | Görevi |
|---|---|
| PC — program sayacı | sonraki komutun adresi |
| IR — komut yazmacı | şu an yürütülen komut |
| MAR / MDR | bellek adresi ve oradan okunan/yazılan veri |
| ACC ve genel yazmaçlar | hesaplamaların ara sonuçları |
| Bayraklar (flags) | sıfır, işaret, elde ve taşma bitleri |
- 1Getir (fetch)
PC'deki adres MAR'a geçer, komut bellekten okunup IR'ye yazılır, PC bir sonraki komuta ilerler.
- 2Çöz (decode)
Denetim birimi işlem kodunu ve işlenenleri belirler.
- 3Yürüt (execute)
ALU hesaplar ya da veri belleğe yazılır/okunur; atlama komutu PC'yi değiştirir. Sonra döngü yinelenir.
memory = [('LOAD', 5), ('ADD', 6), ('STORE', 7), ('PRINT', 7), ('HALT', 0), 20, 22, 0]
pc, acc, executed = 0, 0, 0
while True:
op, addr = memory[pc]
pc += 1
executed += 1
if op == 'LOAD':
acc = memory[addr]
elif op == 'ADD':
acc += memory[addr]
elif op == 'STORE':
memory[addr] = acc
elif op == 'PRINT':
print('memory[7] =', memory[addr])
elif op == 'HALT':
break
print('instructions executed:', executed, '| PC =', pc, '| ACC =', acc)▸ Beklenen çıktı
memory[7] = 42 instructions executed: 5 | PC = 5 | ACC = 42
memory[pc]), PC'yi artır, çöz (if op == …), yürüt.- tprogramın işlemci süresi, s
- Nyürütülen komut sayısı
- CPIkomut başına ortalama saat döngüsü
- fsaat frekansı, Hz
İşlemci başarımının “temel denklemi”: hızlanmak için ya daha az komut (daha iyi algoritma ve derleyici), ya daha küçük CPI (boru hattı, önbellek) ya da daha yüksek frekans gerekir.
Bir program 2 · 10⁹ komut yürütüyor, CPI = 1,5, frekans 3 GHz. İşlemci süresi nedir? Derleyici komut sayısını %20 azaltır ama CPI 1,6'ya çıkarsa ne olur?
Çözümü gösterÇözümü gizle
Yeni sürüm: N = 1,6 · 10⁹, t = 1,6 · 10⁹ · 1,6 / (3 · 10⁹) = 2,56 / 3 ≈ 0,853 s.
Komut başına döngü artsa da toplam süre ≈ %15 azaldı; tek bir ölçüte bakarak karar verilmez.
Bellek hiyerarşisi ve önbellek
İşlemci ana bellekten (RAM) yaklaşık yüz kat hızlıdır; buna von Neumann darboğazı denir. Çözüm bellek hiyerarşisidir: işlemcinin yanında küçük ve hızlı önbellek düzeyleri, uzakta büyük ve yavaş bellekler. Önbellek yerellik ilkesine dayanır: yakın zamanda kullanılan veri yakında yine kullanılır (zamansal yerellik), komşu adresler de öyle (uzamsal yerellik). Bu yüzden bellek 64 baytlık önbellek satırlarıyla getirilir.
| Düzey | Tipik boyut | Yaklaşık gecikme |
|---|---|---|
| Yazmaçlar | birkaç yüz bayt | ≈ 1 döngü |
| L1 önbellek | çekirdek başına 32–64 KB | ≈ 1 ns |
| L2 önbellek | çekirdek başına 0,25–2 MB | ≈ 3–5 ns |
| L3 önbellek | ortak, onlarca MB | ≈ 10–20 ns |
| Ana bellek (RAM) | gigabaytlar | ≈ 60–100 ns |
| SSD | yüzlerce GB – TB | ≈ 20–100 µs |
- AMATortalama bellek erişim süresi
- thitönbellekte isabet olduğunda erişim süresi
- mıskalama oranı
- tmissıskalama cezası (bir sonraki düzeye gitmek)
L1: 1 ns, ıskalama oranı %5. L2: 4 ns, kendisine ulaşan isteklerin %20'sini ıskalıyor. RAM: 100 ns. AMAT'ı bul. L2 olmasaydı ne olurdu?
Çözümü gösterÇözümü gizle
AMAT = 1 + 0,05 · 24 = 1 + 1,2 = 2,2 ns.
L2 olmadan: AMAT = 1 + 0,05 · 100 = 6 ns, yani yaklaşık 2,7 kat yavaş.
Sonuç: önbellek dostu kod (diziyi sırayla gezmek), dağınık erişimlerden (bağlı liste) çok daha hızlıdır.
Tam sayılar: ikiye tümleyen
n bitlik ikiye tümleyende (two's complement) en yüksek bitin ağırlığı negatiftir: −2ⁿ⁻¹. Bu yüzden negatif sayılar 1 ile başlar ve ayrı bir “çıkarıcıya” gerek kalmaz: ALU, a − b'yi a + (−b) olarak hesaplar. Toplamanın kendisi mantık kapılarından kurulur: yarım toplayıcıda toplam biti a XOR b, elde ise a AND b'dir.
- NOT xx'in tüm bitlerinin tersine çevrilmesi
- nbit sayısı (8 bit: −128 … 127)
a) −45'i 8 bitlik ikiye tümleyende yaz. b) 8 bitlik işaretli aritmetikte 100 + 50 kaç olur?
Çözümü gösterÇözümü gizle
Sağlama: −128 + 64 + 16 + 2 + 1 = −45 ✓ (işaretsiz okunursa 211 = 256 − 45).
b) 100 = 01100100, 50 = 00110010, toplam = 10010110.
En yüksek bit 1 oldu → −128 + 16 + 4 + 2 = −106. İki pozitif sayının toplamı negatif çıktı; bu taşmadır (overflow): 150 > 127. Sonuç başa sarar: 150 − 256 = −106.
Kayan noktalı sayılar: IEEE 754
- sişaret biti (1 bit)
- ekaydırılmış üs (8 bit, sapma 127)
- mmantisin kesir kısmı (23 bit; baştaki 1 yazılmaz)
32 bitlik (single) biçim. 64 bitlik double: 1 + 11 + 52 bit, sapma 1023, yaklaşık 15–16 ondalık basamak duyarlık. Python float'u bir double'dır.
−6,25'i IEEE 754 single biçiminde yaz (bitler ve on altılık kod).
Çözümü gösterÇözümü gizle
6,25 = 6 + 0,25 = 110₂ + 0,01₂ = 110,01₂ = 1,1001₂ · 2².
Üs: e = 2 + 127 = 129 = 10000001₂.
Mantis (baştaki 1 olmadan): 1001 ve ardından 19 sıfır.
Bitler: 1 | 10000001 | 10010000000000000000000
4'lü gruplar: 1100 0000 1100 1000 0000 … → C0C80000₁₆.
import struct, math
def to_bits(x, bits=8):
return format(x % 2 ** bits, '0' + str(bits) + 'b')
def from_bits(s):
v = int(s, 2)
return v - 2 ** len(s) if s[0] == '1' else v
print(to_bits(45), to_bits(-45), from_bits('11010011'))
print('100 + 50 in 8 bits:', from_bits(to_bits(100 + 50)))
raw = struct.pack('>f', -6.25).hex()
print('-6.25 as float32:', raw, format(int(raw, 16), '032b'))
print(0.1 + 0.2, 0.1 + 0.2 == 0.3, math.isclose(0.1 + 0.2, 0.3))
print(struct.unpack('>f', struct.pack('>f', 0.1))[0])▸ Beklenen çıktı
00101101 11010011 -45 100 + 50 in 8 bits: -106 -6.25 as float32: c0c80000 11000000110010000000000000000000 0.30000000000000004 False True 0.10000000149011612
x % 2 ** bits sayıyı n bitlik “çembere” sarar; ikiye tümleyenin kendisi budur. struct gerçek 32 biti gösterir: elle bulduğumuz C0C80000 ile aynı. 0,1 ikilik sistemde sonsuz bir kesirdir (0,000110011…₂); bu yüzden float32'de 0,10000000149…, double'da ise 0,1 + 0,2 = 0,30000000000000004 olur.Önemli noktalar
- Von Neumann bilgisayarında program ve veri aynı bellektedir; işlemci getir–çöz–yürüt döngüsünü yineler.
- İşlemci süresi t = N · CPI / f'dir; başarım üç etkene de bağlıdır.
- Önbellek yerelliğe dayanır; AMAT = thit + m · tmiss.
- İkiye tümleyen: −x = NOT x + 1; n bit −2ⁿ⁻¹ … 2ⁿ⁻¹ − 1 aralığını kapsar; aralığın dışına çıkmak taşmadır.
- IEEE 754: işaret, kaydırılmış üs, mantis; 0,1 gibi sayılar tam yazılamaz, bu yüzden float'ları == ile karşılaştırma.
Kendini test et
10 soru. Her doğru cevap XP kazandırır.