Az x86-64 kiterjesztés
Ennyit a szép színes rajzocskákról, és a 64 bitről, lássuk a száraz tényeket és adatokat, melyek már konkrétan az x86-64, tehát az AMD 64 bites kiterjesztésének jellemzői. A 64 bites regiszterekben 64 bit széles memóriacímek férnek el, ezért szükség lesz egy dupla széles utasítás- vagy programmutatóra (instruction/program pointer) is, amely az éppen végrehajtandó utasítás helyére mutat a memóriában (RIP). Az x86-64 gyakorlatilag az x86 64 bitre történő terjesztése, ami azt is jelenti, hogy kompatibilis a már meglévő 32 bites architektúrával. Ennek igen komoly jelentősége van, hiszen nem kell kidobni a már meglévő hardvereket és programokat a futtatáshoz, míg az IA-64 széleskörű bevezetésével ezt nehezen kerülhettük volna el. Használatára végül az Intel is rákényszerült, mert nem hagyhatta, hogy az AMD egyedüliként kínáljon 64 bites x86-os asztali, sőt szerverprocesszort, igaz, az Intel úgy tüntette fel a dolgot, mintha saját fejlesztését adná el Extended Memory 64 Technology (EM64T) név alatt.
A kiterjesztés ugyanakkor azt is jelenti, hogy a kiterjesztett (azaz x86-64) mód használatával a már meglévő regiszterek nem csak dupla (32 helyett 64 bit) szélességűekké válnak, de számuk is megduplázódik (8-ról 16-ra), legalábbis ez a helyzet a GPR-ek esetében. A SIMD-regiszterek (XMM) nem szélesedtek tovább (128 bit), de számuk megduplázódott (8 helyett 16), az x87-es számításokhoz használt regiszterek (ST/MM) szélessége (80 bit) és száma (8) viszont egyáltalán nem változott. A virtuális memóriatartomány címmérete 64 bitre, a fizikai 52 bitesre növekszik, viszont az egyes implementációk esetében lehetnek eltérések, például az Athlon 64 processzorok valójában virtuálisan 48, fizikailag pedig 40 bitet használnak.
Hirdetés
Miért jó mindez nekem? Vagy a programozónak?
A GP regiszterek szélességének megduplázása egyrészt a megcímezhető memóriatartomány növelése miatt fontos. 32 biten maximálisan 4 GB a megcímezhető memória mérete. Biztosan találkoztak már páran azzal a jelenséggel, amikor egy 4 GB memóriával telipakolt gépen az operációs rendszer csak 3,5–3,8 GB-ot látott. Ez annak tulajdonítható, hogy a címzésben részt vesz a videokártyán található memória mennyisége is, ezért azt a 4 GB-ból (232) le kell csippenteni. Egy 64 bites processzoron, ha 64 bites operációs rendszert használunk, többé ez nem fordulhat elő. Az igaz, hogy egyelőre nincs túl sok 1–2 GB-nál több memóriával szerelt PC/notebook, de valamikor valaki azt mondta, hogy 640 kB memória elég lesz mindenre, tehát ne higyjük azt, hogy ez így marad az idők végezetéig. Ennek a tulajdonságnak jelenleg inkább a szerverek világában van jelentősége (pl. óriási adatbázisok, szimuláció, modellezés stb. esetén). A széles memóriacímeknek van egy hátránya is, hiszen ha dupla széles számokkal dolgozunk, akkor azok több tárterületet foglalnak el a memóriában, a gyorsítótárakban, tehát ha véletlenül egy ilyen tárolókomponens válik szűk keresztmetszetté a rendszerben, akkor ez csökkentheti a teljesítményt, feleslegesen.
A regiszterszélesség-duplázás másik fontos mellékhatása, hogy 64 bites egész számokkal dolgozhatunk. Ha egy átlagos asztali PC-t veszünk alapul, ennek gyakorlatilag nincs semmilyen hatása ránk nézve, hiszen az átlagos, mindennapi használatban lévő programok bőven megelégednek a 32 bites egész számokkal is. Viszont vannak területek, ahol jól jöhet ez a tulajdonság: titkosító algoritmusok, esetlegesen 64 bites színmélységgel dolgozó, képalkotásra készített alkalmazások, CAD/CAM tervezőprogramok esetében vagy különböző tudományos számításokat végző kutatási projektekben, és még lehetne őket sorolni – ezek is inkább a szerverek, munkaállomások világában elterjedtek, mintsem az otthon, Quake 3-ra használt masinákon.
32 bites kód | 64 bites kód |
mov eax,[SZAM1+00] ;1.szám alsó 32 bit mov ebx,[SZAM1+04] ;1.szám felső 32 bit mov ecx,[SZAM2+00] ;2.szám alsó 32 bit mov edx,[SZAM2+04] ;2.szám felső 32 bit add eax,ecx ;alsó 32 bitek összeadása a 33. bit a carry adc ebx,edx ;felső 32 bitek összeadása a 65. bit a carry mov [SZAM3+00],eax ;eredmény alsó 32 bit mov [SZAM3+04],ebx ;eredmény felső 32 bit | mov rax,[SZAM1] ;1.szám mov rbx,[SZAM2] ;2.szám add rax,rbx |
Elképzelhető egy olyan forgatókönyv, amikor 32 bites processzoron futtatunk 64 bites kódot. Könnyen belátható, hogy ilyenkor milyen előnyei vannak a 64 bites processzornak. Vegyük például két 64 bites integer betöltését. Egy 32 bites processzoron ehhez négy regiszterre és négy load műveletre van szükség, miközben egy 64 bites processzoron csak két regiszter és két load segítségével végezzük el ugyanazt a munkát. Ugyanez igaz a store-okra is. Két 64 bites integer összeadásánál a 64 bites processzor egyszerűen összeadja két regiszter tartalmát, míg egy 32 bites processzoron szükség van egy sima összeadásra, egy carry flages összeadásra, majd két loadra, ami az eredmény alsó és felső 32 bitjét betölti két regiszterbe, vagyis eszméletlen módon képes megnőni az egymástól függő utasítások száma, és akkor még nem ejtettünk szót a szorzásról, osztásról, ahol tovább romlik a helyzet. Természetesen ezt csak szemléltetésképpen jegyeztük meg, valójában 32 bites processzoron 64 bites kódot futtatni eleve elvetemült ötlet.
A mi szempontunkból sokkal fontosabb a regiszterek számának megduplázása, ami viszont érzékelhető teljesítménytöbblettel jár abban az esetben, ha a programozó kihasználja ezt a lehetőséget. A processzor – nevezzük úgy – saját memóriakészlete megduplázódott. Az x86 egyik legnagyobb baja, hogy kevés regiszterrel rendelkezik. Az IA-64 például 128 GPR-t tartalmaz, de hogy maradjunk a PC-k világánál, a PowerPC 970-ben (G5) 32 integer regiszter található, tehát négyszerese az x86-énak, és akkor még nem szóltunk az FP és SIMD regiszterekről. Minél kevesebb a regiszter, annál inkább a cache-re és a memóriára kell hagyatkozni, ezek elérése pedig lényegesen lassabb a regisztereknél. Ha a programozó számára több regiszter áll rendelkezésre, hatékonyabb kódot képes írni, amiben kevesebb a memóriaelérés. A regiszterek kihasználtságának köszönhetően javul a CPU hatékonysága, miközben a rendszerbusz terhelése csökken, ami ez esetben elvárható és pozitív jelenség. Az x86 esetében éppen a kevés regiszter miatt kezdték el alkalmazni a regiszter-átnevezés technikát, ennek segítségével a WAR (write-after-read) és a WAW (write-after-write) függőségeket lehet csökkenteni. A processzorban a programozó számára is látható architekturális regisztereken felül található adott számú belső, csak a CPU számára látható regiszter is, melyeket akkor használ, amikor az OoO végrehajtás dolgozik (például x regiszter tartalmát ki kell írni adott memóriacímre, az utána következő utasítás pedig x regiszterbe be akar olvasni egy adatot, de nem teheti meg, amíg az első utasítás végre nem hajtódott, holott nincs is köztük függőség – ilyenkor a CPU bevet még egy belső regisztert, hogy az utasítások végrehajtása folyamatos legyen). A regiszter-átnevezés ellenére is jó, ha minél több programozható regiszter áll rendelkezésünkre, hiszen így a programozó számára megadatik a lehetőség, hogy saját kezűleg, statikusan optimalizálja a függőségeket a processzor helyett, amely az OoO végrehajtás révén tenné meg ugyanezt.
Üzemmódok
Az x86-64-es processzorok három úgynevezett üzemmódot ismernek. Legacy módról beszélhetünk, amikor 32 bites operációs rendszert használunk, és ez alatt 32 bites programokat futtatunk. Ebben az esetben nem használhatjuk az x86-64 újításait, lényegében úgy dolgozunk, mintha mi sem történt volna (erről szól a visszafelé kompatibilitás). Ha long módba „kapcsolunk”, amihez szükség van egy 64 bites OS-re, már kihasználhatóvá válnak az x86-64 adottságai. Kompatibilis almódnak nevezzük azt, amikor 64 bites operációs rendszer alatt 32 bites programokat futtatunk. Ezesetben a program nem fordítódott újra, ezért nem tud profitálni az x86-64-ből, viszont nem is kellene, hogy különbséget érezzünk a 32 bites módhoz képest. Ahhoz, hogy kihasználhassuk teljes egészében az x86-64-et, 64 bites almódot kell használni, azaz a programot újra kell fordítani vagy újra kell írni, hogy használja az új regisztereket, címzést stb. Az üzemmódtól függ, hogy az alapértelmezett operandus- és címzésszélesség pontosan mennyi is, legacy módban természetesen maximum 32 bit, kompatibilis módban szintén, és csak 64 bites módban élhetünk a 64 bites címzéssel, viszont az integer operandusok szélessége alapértelmezés szerint továbbra is 32 bit, ami egy praktikus megfontolás következménye, hiszen a 64 bites egész számolásra ritkán van szükség, feleslegesen pedig nem érdemes terhelni a memóriát, rendszerbuszt, gyorsítótárakat, regisztereket.
Emiatt a 64 bites operandusok használatát külön jelezni kell a kódban, erre hivatott a REX prefix (előtag), ez 1 bájtos méretével növeli a kódot, az AMD számításai szerint 64 bites módban az utasítások átlagos hossza 0,4 bájttal nő. Az Intel a REX prefix használatával kapcsolatban kiemeli, hogy a kód méretének növekedése miatt nőhet a cache-missek száma, ezért ha az adott algoritmus megvalósításához elegendő a nyolc „megszokott” regiszter, akkor ne erőltessük a REX prefix használatát. A prefix értéke 40-tól 4F-ig terjedhet, és az opkódban az utasításhoz minél közelebb kell elhelyezkednie. Egy ADD EAX,8 gépi kódban 83 C0 08 lenne, viszont ha 64 bites számmal dolgozunk, akkor az ADD RAX,8 már 48 83 C0 08-ként lesz végrehajtva.
Buktatók
Ilyen nincs túl sok, de azért van egy-két dolog, amit ki lehetne emelni. Az egyik a REX prefix használatával kapcsolatos. Mint tudjuk, alkalmazása növeli a kód méretét, márpedig ez nem tesz jót a Core processzoroknak, melyek órajelenként 16 bájtnyi utasítást hívnak be elődekódolásra. Az AMD a K10-ben a fetch szélességét (talán éppen emiatt) 32 bájtra növelte, tehát esetében ez nem okoz akkora bonyodalmat, viszont a Core 64 bites módban így átlagosan kevesebb utasítást képes végrehajtani.
Szintén a Core processzorokat érinti, és a REX prefix használatából fakad a tény, hogy 64 bites módban nem működik hatékonyan a macro-op fúzió. Mit jelent ez? A macro-op fúzió segítségével a Core az olyan gyakori x86-os utasításpárokat (macro-op), mint például cmp vagy test és jmp/ja/jae stb. (összehasonlítás és ugrás, feltételes elágazás) összevonhatja egyetlen utasításba (micro-op), ezzel pedig csökken a processzorra háruló munka, így adott idő alatt nő az elvégzett utasítások száma. Az Intel szerint az utasítások körülbelül 15%-a feltételes elágazás, ami jól hangzik a macro-op fúzió szemszögéből, de tudni kell azt is, hogy ez a funkció csak akkor igazán hatékony, ha az utasítások hossza 4 bájtnál rövidebb (így a CPU órajelenként 4 vagy 4+1 utasítást képes behívni, hiszen 16 bájt széles az előbehívó), emiatt a mérnökök úgy tippelik, hogy a macro-op fúzió hozzávetőleg 4–5%-ot segít a processzornak. Mindez azt jelenti, hogy 64 biten a Core processzorok teljesítménye elméletben csökkenhet 4–5%-ot, mert a REX prefix növeli a kódot (amikor kihasználjuk a szélesebb/több regisztert), így az utasításbehívó többé nem képes optimálisan működni, azaz 4 utasítás/ciklus behívást végrehajtani. De ugyanez elmondható az SSE kódra is, amelynek átlagosan 6 bájt körül alakul az utasításszélessége, igaz, ott ritkábban fordulnak elő feltételes ugrások.
A cikk még nem ért véget, kérlek, lapozz!