Éppen aktuális témáról lesz szó ebben a kis irományban. Napjaink
számítógép-piacának meghatározója az Intel Pentium II (továbbiakban P2)
processzora, melyrôl sok szépet s jót lehet hallani. De vajon valóban olyan
eget rengetô a teljesítménye, ahogy híresztelik? Ha így van, milyen technikai
megoldásoknak köszönhetô ez? Miképpen is mûködik - leegyszerûsítve - egy P2?
Ha ezekre a kérdésekre keresed a választ, jó helyen jársz!
Nos, a várakozásokkal ellentétben ez a cikk nem a P2-rol fog szólni.
Sokkal inkább a P2-t megelôzô Intel processzorokról, az i486-rol, Pentiumról
és a Pentium Pro-ról. Kezdjük is elôször az alapoknál, hogyan és miképpen
képes a Pentium már 60 MHz-en megverni a 100 MHz-es i486DX4-et?
A 386 és 486 generációs procik a programvégrehajtást "klasszikus" módon
végzik: az utasításokat szépen sorban, egymás után - egyesével -
végrehajtják. Bár a 486-os már képes volt a CPU és az FPU utasításokat
párhuzamosan végrehajtani, ez a lebegôpontos számolást igénylô programokra
jó hatással volt, azonban az FPU-t nem igénylô alkalmazásokat nem tudta
gyorsítani.
A processzoron belül az utasítás végrehajtás négy fázisra bontható, és
mindegyikért egy-egy egység felelôs:
1) Fetch Unit
2) Decode Unit
3) Execute Unit (az összes végrehajtó egység közös neve)
4) Retire Unit
Titokzatos nevek, de mit is jelentenek?
A Fetch Unit feladata, hogy a memóriából a feldolgozás helyére, a prociba
kerüljön a soron következô utasításokat tartalmazó memóriarészlet. A Decode
Unit azért felel, hogy a mûvelet elvégzéséhez minden feltétel rendelkezésre
álljon. Egyrészt a mûvelet elvégzéséhez szükséges operandusokat tölti be a
processzorba, másrészt értelmezi, milyen mûvelet elvégzését írja elô az
adott utasítás. A Decode Unit alakítja át az utasításokat az Execute Unit
számára értelmezhetô un. micro operation-okre (továbbiakban uop). Az
egyszerûbb utasításokból 1 uop lesz, a komplikáltabbakból 2, 3, 4 stb., az
egészen bonyolultak akár több száz uopra is bomolhatnak. Ezek az uop-ok a
Decode Unitot elhagyva átlépnek a Execute Unitba. Ez az egység megfogja
az uopokat, és szépen sorban végrehajtja ôket, majd az eredmény átvándorol
a Retire Unitba. Ebben az utolsó lépésben az eredmény a megfelelô helyre
kerül (pl. a memóriába vagy portra íródik, regiszterben tárolódik stb), a
végrehajtott uopok pedig kikerülnek a processzorból. Itt véget is ér a
folyamat, és minden kezdôdik elölrôl.
Mar a 386/486 generációnál is alkalmaztak un. pipeline technikát, amivel
lehetôvé vált, hogy a 4 egység párhuzamosan dolgozzon. így pl. amíg a
Execute Unit egy uop végrehajtásával van elfoglalva, a Fetch és Decode
Unitok már a következô gépi kódú utasítást hozzak be a memóriából, és szedik
szét uopokra. Ezzel a módszerrel a 4 egység mûködése folyamatossá tehetô.
A Pentiumnál nagy elôrelépést jelentett, hogy az utolsó 3 egységet
megduplázták, így lehetôvé vált két utasítás párhuzamos dekódolása és
végrehajtása, majd az eredmény elôállítása. Itt azonban elôjön egy súlyos
probléma, amit "adatfüggésnek" szoktak nevezni. Lássuk, mi is történik, ha
a következô kódrészletre kerül a végrehajtás egy Pentium procin:
a=2
b=3
Ezt a két utasítást a Pentium párhuzamosan hajtja végre, vagyis az eredmény
fele annyi idô alatt áll elô, mint a "hagyományos" úton.
a=2
a=3
Hoppá, itt mar gubanc van! Mindkét utasítás ugyanabba a változóba ír!
A Pentium sajnos nem olyan ügyes, hogy rájöjjön, melyik utasítás van elôl,
és melyik hátul, így az ilyen esetekben a párhuzamos végrehajtás helyett
visszakapcsol a hagyományos módba. Tehát elôször végrehajtja az elsô, majd
a második utasítást, szépen egymás után. Ennek a bonyodalomnak a neve: WAW
(Write After Write) függés, mely szerencsére nagyon ritkán fordul elô a
programokban, nem úgy, mint a WAR (Write After Read):
c=a
a=b
A második utasítás az elsô által olvasott helyre próbál írni, ezt is
hagyományos módon hajtja végre a Pentium.
a=b+2
c=a*a
Ezt a kódrészletet sem tudja a Pentium párhuzamosan futtatni, hiszen a
második utasítás eredménye az elôzô utasítástól függ (RAW - Read After
Write - függés), így itt is csak a hagyományos úton mehet a végrehajtás.
Mellesleg, ez egy olyan probléma, melyre nincs jobb megoldás, mint amit
a Pentium tesz.
A Pentiumok másik nagy újítása, a dinamikus Branch Prediction a feltételes
ugrások útjának megbecsülésére született. Erre azért volt szükség, mert az
utasítások közé ékelôdô feltételes elágazásoknál megállt a pipeline-os
végrehajtás, vagyis a Fetch és a Decode Unit nem tudott "elôre dolgozni",
amíg az Execution Unit meg nem állapította a feltételes elágazás eredményét.
Ezért egy feltételes elágazásokkal teletûzdelt programot (pl. sok-sok IF
vagy CASE utasítás Pascalban vagy C-ben) a Pentium akár a 486-osoknál is
lassabban hajtaná végre a Branch Prediction nélkül. A Pentium elôre
megbecsüli az elágazás eredményét, és ennek megfelelôen fog elôre dolgozni
a Fetch és a Decode Unit. Ha a becslés sikeres volt, akkor minden rendben,
az elágazás valódi végrehajtása után a proci fennakadás nélkül dolgozik
tovább. Ha azonban a becsült irány nem stimmel, akkor a Fetch és Decode Unit
munkája hiábavaló volt, hiszen az általuk dekódolt utasításokra nincs
szükseg, azokat egyszerûen eldobja a proci. Ettôl viszont kiürül a pipeline,
és ebbôl fakadóan a programvégrehajtás átmenetileg szünetelni fog, mert az
Execution Unitnak meg kell várnia, amíg a Fetch és a Decode Unitok átadják
neki a most már helyes uopokat. S mindez több ciklusnyi veszteset okoz a
végrehajtásban. Éppen ezért nagyon fontos a minél pontosabb ugrás elôrejelzés.
A Pentium Pro (továbbiakban P6) architektúrájában rengeteg újítást hordoz,
a legtöbb a Pentium hibáinak kiküszöbölésére irányul. A WAW és a WAR függés
elhárítására bevezettek a Register Renaming nevû trükköt, mely egyszerûen
annyit csinál, hogy a processzor belsô regiszterkészletét kiegészíti egy
csomó átmeneti tároló hellyel.
a=2
a=3
így ezt a kódrészletet is végre tudja hajtani párhuzamosan, hiszen a 2 és
a 3 csak átmeneti tároló helyre fognak kerülni, és az újabb eredményt (3)
fogja a Retire Unit a végleges helyre tenni (a-ba).
A másik varázslatot röviden OOOE-nek szoktak hívni, ez szép hosszan
kiírva: Out Of Order Execution. A P6-ban gyökeresen megváltoztatták a
végrehajtás menetét, és a 4 nagy egység mellé létrehoztak az Instruction
Pool-t és a ReOrdering Buffer-t. így a Fetch Unit-ból az utasítások a
Decode Unit-ba vándorolnak, innen pedig az uopok átkerülnek az Instruction
Pool-ba. Ebben a nagy "medencében" szépen megfér egymás mellett akár 20 uop
is! Az Execution Unit az Instruction Poolból veszi ki a feldolgozásra szánt
uopokat, majd az eredménnyel együtt átrakja ôket a ReOrdering Bufferbe,
ahonnét aztán a Retire Unit veszi ki ôket, és az eredményt a már ismert
módon létrehozza. Ebbôl így önmagában nem származna különösebb elônye a
P6-nak... Azonban az OOOE valódi jelentôsége az, hogy a processzor képes az
utasításokat nem a fizikai sorrendjükben végrehajtani. Ennek gyakorlati
megvalósítását segíti az Instruction Pool, hiszen abban nem sorrendben
tárolódnak az uopok, hanem össze-vissza. így az Execution Unit szabadon
válogathat az uopok között, emiatt a programvégrehajtás mindig optimális.
Ez egy remek dolog, mivel így az összes függést "át tudja lépni" a
proci, és folytathatja a kód végrehajtását a további, esetleg kevésbé
bonyodalmas utasításokkal. Természetesen a Retire Unit a ReOrdering Buffer
segítségével a fizikai sorrendnek megfelelôen állítja elô az eredményt, így
az elôzô proci generációkkal megmarad a kompatibilitás.
a=2
b=a
c=3
d=d+1
e=e*5
Ebben az esetben az elsô utasítás végrehajtása után nem a másodikat, hanem
a harmadikat fogja a P6 végrehajtani, majd ha a függés miatt lelassult
elsô és második utasítás még mindig nem fejezôdött be, akkor megy szépen
tovább a negyedik majd ötödik utasításra. A párhuzamos végrehajtást a P6-ban
a Pentium két pipeline-ja mellett még három, speciális pipeline és a
továbbfejlesztett Branch Prediction Unit is segíti. A becslés hatékonysága
ugyan javult valamennyit a Pentiumhoz viszonyítva, azonban még mindig 90%
körül mozog, ami nem megfelelô a P6 ultrahosszú pipeline-jához. Hiszen ha
tévesztésre kerül a sor, akkor akar 10-20 elôre dekódolt utasítást kénytelen
a processzor az Instruction Pool-ból eldobni, a feldolgozást minden egység
elölrôl kénytelen kezdeni. A sok-sok utasítás végrehajtó pipeline mûködése jó
idôre le fog állni emiatt.
A bevezetôben említettem, hogy ez a cikk nem a P2-rôl fog szólni. Most
azonban, a végéhez közeledve lássuk, miben is különbözik a P2 a P6-tol, és
mi lapul a P2 változatainak fantázianevei mögött:
Pentium Pro: az eddig tárgyalt proci (256 KB, 512 KB vagy 1 MB
L2 cache)
Pentium II: Pentium Pro + MMX (512 KB L2 cache)
Klamath: a P2 233-300 MHz-es változatainak kódneve
Deschutes: a P2 333-450 MHz-es változatainak kódneve
Mobile Deschutes: a P2 notebookokba szánt változata
Celeron: költségcsökkentett P2 (0 KB L2 cache)
Mendocino/CeleronA: költségcsökkentett P2 (128 KB L2 cache)
Dixon: költségcsökkentett P2 (256 KB L2 cache)
Katmai: Pentium II + KNI (Intel 3D gyorsító utasítások)
P2 Xeon: a P2 szerverekbe szánt változata (512 KB, 1 MB vagy
2 MB L2 cache)
Jól látható, hogy a Pentium II nem más, mint egy MMX-szel és pár aprósággal
kiegészített Pentium Pro. A P2 azonban azonos órajelen lassabb, mint a P6,
mert a beépített L2 cache csak a proci órajelének felével ketyeg a P2-ben,
míg a P6-ban a CPU és az L2 cache órajele megegyezik.
A P6 un. "full speed" L2 cache-et a Xeonban és Mendocinoban hozta újra
vissza az Intel. A Dixon és a Katmai is ezt a vonalat fogja folytatni.
(c) 1998 Fiery
A Dune News köszönetet mond a cikkért Fiery-nek az ASMDEMO írójának és fejlesztôjének. Kösz!