Egy C-ben írt tömbfeldolgozó függvény ugyanazzal az eredménnyel futhat két különböző ARM processzoron, miközben az egyik lényegesen hamarabb végez. Mi biztosítja az azonos eredményt, és mi okozza az eltérő futási időt?
A választ két szinten keressük:
Ez mérnöki szempontból alapvető különbség. A program helyességét nem alapozhatjuk egy adott processzor véletlen belső tulajdonságára; a teljesítményét viszont nem jósolhatjuk meg pusztán az utasítások jelentéséből.
Előfeltétel a Számrendszerek és bitszintű reprezentáció anyaga. Az alfejezet minden konkrét gépi példája A64 ISA, AArch64, ARMv8-A alapprofil, little-endian környezetben. Cortex-M-utasítást itt nem használunk.
A tárolt program elve (stored-program concept) szerint az utasítások kódolt bitmintái memóriában tárolhatók, ugyanúgy, mint a feldolgozott adatok. A Von Neumann-modell közös memóriában kezeli a programot és az adatokat; a processzor ebből olvassa ki a végrehajtandó utasításokat.
A „program mint adat” nem azt jelenti, hogy minden adat végrehajtható. Azt jelenti, hogy egy utasításnak is van tárolható, másolható, fájlba írható reprezentációja. Az assembler szöveges utasításnevekből ilyen gépi kódot állít elő.
Ugyanazokat a bájtokat különböző eszközök másként értelmezhetik:
A tényleges végrehajthatóságot a memória-hozzáférési jogosultságok is korlátozzák. Egy írható adatlap nem feltétlenül végrehajtható.
A mai processzorok gyakran külön utasítás- és adatgyorsítótárat használnak. Ez nem cáfolja a tárolt program elvét: a programozói modell lehet közös címtérre épülő, miközben a belső adatút részben szétválik. Nem következik belőle az sem, hogy minden memóriaforgalom egyetlen fizikai vezetékkötegen halad.
Az ISA több, mint az utasításnevek listája. Meghatározza többek között:
A mikroarchitektúra ezzel szemben olyan döntéseket tartalmazhat, mint a végrehajtóegységek száma, a gyorsítótárak szervezése vagy az egyszerre feldolgozható utasítások mennyisége.
| Kérdés | Elsődleges szint |
|---|---|
Mit jelent az A64 ADD W0, W1, #5? |
ISA |
Az A64 W0 írása hogyan hat az A64 X0 felső
bitjeire? |
ISA |
| Hány egész összeadás indítható egy órajelciklusban? | Mikroarchitektúra |
| Mekkora egy adott processzor adatgyorsítótára? | Konkrét megvalósítás |
| Mennyi idő alatt futott le a laborprogram? | Mérés, meghatározott környezetben |
| Melyik szám jelöli Linux alatt a folyamatkilépést? | Operációsrendszer-interfész, nem ISA |
Az azonos ISA-t megvalósító processzoroknak az előírt architekturális hatásokat kell biztosítaniuk, nem azonos belső lépéseket vagy futási időt. A bináris kompatibilitáshoz ezenfelül a használt kiterjesztések, az operációs rendszer és a bináris interfész egyezése is szükséges.
A regiszter (register) a processzoron belüli, névvel elérhető tárolóhely. Az utasítás közvetlenül megnevezheti, mely regiszterek tartalmával dolgozzon. A programozó által látható regiszterek együttese az architekturális regiszterkészlet.
A regiszterfájl (register file) a regiszterértékeket tároló és kiszolgáló belső szerkezet. Egyszerű modellben megfelel a látható regiszterkészletnek; összetettebb processzorban több fizikai tárolóhely is lehet mögötte. Ennek részleteit egyelőre nem kell ismernünk.
Az aritmetikai-logikai egység (arithmetic logic unit, ALU) például összeadást és bitenkénti logikai műveleteket végez. Nem minden utasítást ugyanaz az egység hajt végre: a memóriaeléréshez vagy lebegőpontos számításhoz külön egységek tartozhatnak.
A busz (bus) kommunikációs út, amelyen címek, adatok és vezérlési információk továbbíthatók. Mai rendszerekben gyakran összetett összeköttetési hálózat helyettesíti az egyszerű közös buszt. Szélessége nem szükségképpen egyezik a processzor regiszterszélességével.
A memóriahierarchia (memory hierarchy) eltérő kapacitású és elérési költségű tárolási szinteket kapcsol össze:
| Szint | Bevezető szerep |
|---|---|
| Regiszterek | Az utasítások közvetlen operandusai |
| Gyorsítótárak, cache-ek | Memóriatartalom gyorsan elérhető másolatai |
| Főmemória | A futó program nagyobb adat- és kódterülete |
| Háttértár | Tartós fájltárolás; nem közönséges regiszteroperandus-forrás |
Az A64 load/store jellegű ISA: az itt használt egész összeadás regisztereken dolgozik. A memóriában lévő értéket előbb betöltjük, az eredményt pedig külön utasítással írjuk vissza.
A szóméret (word size) kifejezés önmagában nem elég pontos: jelenthet természetes feldolgozási szélességet, de lehet dokumentációban rögzített adategység is. Az A64 terminológiájában a word 32 bites, a doubleword 64 bites mennyiség, noha az AArch64 általános célú regiszterei 64 bitesek.
A64-en az általános célú regiszterek X0–X30
neve a 64 bites nézetet jelöli; W0–W30
ugyanazon regiszterek alsó 32 bites nézete. Nem két független
regiszterkészletről van szó.
Kiemelten fontos A64-szabály:
Egy
Wncélregiszter írása nullázza a megfelelőXnregiszter felső 32 bitjét.
Például, előjel nélküli olvasatban:
| A64-állapot | X1 teljes tartalma |
W1 nézet |
|---|---|---|
| Kezdetben minden bit egy | 0xffffffffffffffff |
0xffffffff |
A W1 célregiszterbe 37 kerül |
0x0000000000000025 |
0x00000025 |
Ez ISA-szerződés, nem egy adott processzor optimalizálása.
A 64 bites regiszter nem jelenti azt sem, hogy minden utasítás 64 bites kódolású: az A64 utasításai 32 bites kódolásúak. A regiszterszélességből továbbá nem következik, hogy minden lehetséges 64 bites cím használható. A C-típusok és a gépi nézetek megfeleltetésénél pedig a célplatform adatmodelljét is figyelembe kell venni.
Az utasítás-elővételezés–dekódolás–végrehajtás (fetch–decode–execute) ciklus fogalmi modell:
PC) által meghatározott
helyről utasításkódot kér le. Ezt gyakran az utasítás-gyorsítótár
szolgáltatja.A64-ben szekvenciális haladáskor a következő utasítás címe 4 bájttal nagyobb; elágazás ezt megváltoztathatja.
Ez nem négy kötelező órajelciklus! Egy tényleges processzor finomabban bonthatja a munkát, és több utasítás különböző feldolgozási lépéseit átfedheti. Az architekturális eredménynek ettől még meg kell felelnie az ISA szabályainak.
Az órajel (clock) időzítési referencia. Egy órajelciklus (clock cycle) időtartama:
ahol másodpercben értendő, pedig az órajelfrekvencia hertzben.
Elméleti, levezetett példa: állandó 2 GHz mellett egy ciklus 0,5 ns. Ha valamely végrehajtás pontosan 12 ciklust igényel, az ideje 6 ns. Ez nem állítás a későbbi labor futási idejéről.
Két további mennyiséget különítünk el:
Egy hipotetikus, futószalagos egység eredménye lehet 3 ciklus után kész, miközben független műveletet minden ciklusban fogadhat. Egymás eredményére váró műveleteknél ez az átfedés nem használható ki ugyanígy.
Az „egy utasítás egy ciklus” ezért hibás általánosítás. Egy utasítás több ciklusig tarthat, több utasítás feldolgozása átfedhet, memóriaeléréskor pedig várakozás keletkezhet. A QEMU user-mode futási ideje nem alakítható át a célprocesszor hardverhű ciklusszámává.
Munkakönyvtár: ~/arm-labor/a64/a003/ —
az alfejezet minden laborfájlja ide kerül, és minden parancs innen
futtatandó (a könyvtárrendet a Labor-alapkörnyezet alfejezet írja le).
mkdir -p ~/arm-labor/a64/a003
cd ~/arm-labor/a64/a003Szerepe a fejezetben: ez a példa célzott kiegészítés: egyetlen jelenséget mutat meg külön builddel, ezért első olvasáskor kihagyható. A fejezet végigvitt projektjének ide tartozó lépése — amely nem hagyható ki — a szakasz végén, a „Végigvitt projekt” részben található.
A labor feladata: egy memóriában tárolt, előjel nélküli 32 bites értékhez adjunk 5-öt, tároljuk az eredményt, majd adjuk vissza kilépési állapotként.
C-szinten a művelet eredmeny = bemenet + 5u. Most
láthatóvá tesszük a mögötte álló adatmozgást is.
A példa előre használ néhány eszközlánc-elemet; telepítésüket a Labor-alapkörnyezet: eszközlánc telepítése és verifikáció tárgyalja. Nincs szükség fizikai ARM gépre.
Mentsük lab-isa.s néven:
// A64 ISA, ARMv8-A; GNU as, AArch64-cél; little-endian.
.section .text
.balign 4
.global _start
.type _start, %function
_start:
adr x2, lab_values
movn x1, #0
ldr w1, [x2]
add w0, w1, #5
str w0, [x2, #4]
lab_before_exit:
movz x8, #93
svc #0
.size _start, . - _start
.section .data
.balign 4
lab_values:
.word 37
.word 0
.section .note.GNU-stack,"",%progbitsA ponttal kezdődő sorok assemblerdirektívák, nem
processzorutasítások. Ebben a GNU as, AArch64-célú példában a
.word 32 bites adatot hoz létre, a .balign 4
pedig 4 bájtos igazítást kér. A címeket a linker rendezi el; nem
használunk kézzel választott memóriacímeket.
Az A64-utasítások jelentése és releváns kódolási korlátai:
| A64-utasítás | Hatás és korlát |
|---|---|
ADR X2, lab_values |
PC-relatív címet képez. Eltérése előjeles 21 bites bájteltérés: −1 MiB és +1 MiB − 1 bájt között. |
MOVN X1, #0 |
A nullás közvetlen érték invertálásával minden bitet egyre állít. Az immediate 16 bites; 64 bites alakban eltolása 0, 16, 32 vagy 48 lehet, itt 0. |
LDR W1, [X2] |
4 bájtot tölt be. A használt unsigned-offset alak 12 bites, 4 bájttal skálázott eltérést enged; itt az eltérés 0. |
ADD W0, W1, #5 |
32 bites összeadás. Az immediate 12 bites, opcionálisan 12 bites balra tolással; itt nincs eltolás. |
STR W0, [X2, #4] |
4 bájtot tárol a második adatmezőbe. Az eltérés 4 többszöröse, ebben az alakban 0–16380 bájt lehet. |
MOVZ X8, #93 |
Nullázás mellett beírja a 16 bites immediate értékét; az eltolási
lehetőségek azonosak a fenti 64 bites MOVN
lehetőségeivel. |
SVC #0 |
Supervisor-hívást kezdeményez; az immediate 16 bites. Linux/AArch64 alatt itt a rendszerhívási interfészt használjuk. |
A Linux/AArch64 konvencióban az A64 X8 = 93 választja az
exit rendszerhívást, az A64 X0 pedig az
állapot argumentuma. Ez Linux-szabály: az A64 SVC #0
önmagában nem jelent „kilépést”.
# Target: qemu-user, Linux/AArch64, little-endian.
# Host baseline: Ubuntu Server 24.04 LTS x86_64.
aarch64-linux-gnu-as -march=armv8-a -g -o lab-isa.o lab-isa.s
aarch64-linux-gnu-ld -e _start -o lab-isa lab-isa.o
aarch64-linux-gnu-objdump -d ./lab-isa
if qemu-aarch64 ./lab-isa; then
lab_status=0
else
lab_status=$?
fi
printf 'Kilépési állapot: %d\n' "$lab_status"Várt, levezetett kimenet:
Kilépési állapot: 42. A program nem ír szöveget; ezt a
shell írja ki. A 42 itt szándékos, nem hibát jelző eredmény.
A64-en a lab_before_exit címke elérésekor:
| Megfigyelhető állapot | Várt érték, előjel nélküli olvasat |
|---|---|
X1 |
0x0000000000000025 |
X0 |
0x000000000000002a |
X2 |
A lab_values futásidejű
címe |
| Első 32 bites adatmező | 37 |
| Második 32 bites adatmező | 42 |
Little-endian tárolásnál a második mező bájtjai növekvő cím szerint:
0x2a, 0x00, 0x00,
0x00.
Az első terminálban:
# Target: qemu-user, Linux/AArch64; GDB-kapcsolatra vár.
qemu-aarch64 -g 1234 ./lab-isaA második terminálban:
# Target: az előző qemu-user folyamat AArch64 vendégállapota.
# A GDB kisbetűs nevei az A64 X0, X1, X2 regisztereket jelölik.
gdb-multiarch -q ./lab-isa \
-ex 'target remote localhost:1234' \
-ex 'break lab_before_exit' \
-ex 'continue' \
-ex 'info registers x0 x1 x2' \
-ex 'x/2uw $x2' \
-ex 'continue' \
-ex 'quit'A töréspont a kilépés előtt állítja meg a programot. Az
x/2uw két, előjel nélküli decimális, 4 bájtos
memóriaegységet jelenít meg; várt tartalmuk 37 és 42. A folytatás után a
QEMU-folyamat várt kilépési állapota ismét 42.
lab-hidden utasításai mint ISA-szerződésA Miért assembly alfejezetben készült
lab-hidden programon most az ISA-szerződés két elemét
azonosítjuk: a rögzített utasításkódolást és a regiszternézetet. A
vizsgálathoz elég a kész bináris; ugyanebben a könyvtárban
dolgozunk:
# Hostoldali vizsgálat; a fájl célja AArch64 Linux, qemu-user.
cp ~/arm-labor/a64/a001/lab-hidden .
aarch64-linux-gnu-objdump -d --disassemble=lab_next_u lab-hiddenLevezetett, várt részlet (a címek a buildtől függenek):
<lab_next_u>:
...: 11000400 add w0, w0, #0x1
...: d65f03c0 ret
Mit mutat ez az alfejezet fogalmaival?
11000400, d65f03c0). A 32 bites, rögzített
hosszú kódolás az A64 ISA-szerződés része, nem a fordító döntése.add a
w0-t, vagyis az X0 32 bites nézetét használja;
a uint32_t szélessége így jelenik meg a gépi szinten.add hány
órajelciklus alatt fut le, vagy hogy a mag milyen futószalagon hajtja
végre — ez a mikroarchitektúra kérdése, a bináris erről nem árul el
semmit.„Az A64 X1 felső fele biztosan
megmarad.”
Hipotézis: az A64 W1 írása csak az alsó biteket módosítja.
Ellenpróba: a program előbb minden bitet egyre állít, majd betölt az A64
W1 célregiszterbe. A GDB-ben látott nullás felső fél
cáfolja a hipotézist.
„A fájlban még nulla van, tehát a tárolás nem
működött.”
Az objdump az ELF-fájlt vizsgálja, nem a futó folyamat
módosított adatmemóriáját. A futásidejű állapotot a GDB-ben
ellenőrizzük.
„A 42 utasításszámot vagy ciklusszámot
jelent.”
Nem: ez a kiszámított eredmény, amelyet kilépési állapotként
továbbítunk. A helyességi teszt nem teljesítménymérés.
„Az ADR minden távolságot elér.”
Nem: a relatív tartomány korlátozott. Tartományhiba esetén a linker
diagnosztikáját kell vizsgálni, nem találomra más címet beírni.
(magyarázó, alapszint — cél:
megértés-ellenőrzés) Sorold az ISA, mikroarchitektúra vagy
mérési eredmény kategóriába: az A64 utasításhossz; az adatgyorsítótár
mérete; az A64 W1 írásának hatása az A64 X1
felső felére; egy futás falióraideje.
(implementációs, alapszint — cél: rutinépítés) Csak az összeadás közvetlen értékét módosítsd úgy, hogy a program 44-et adjon vissza. Építsd újra az előbbi parancsokkal, és ellenőrizd a második adatmezőt is.
(hibakeresési, középszint — cél: rutinépítés)
Kontrollált hibaként cseréld az A64 ADD utasításban az 5-öt
6-ra. Írd le a tünet–hipotézis–adatgyűjtés–javítás–ellenőrzés láncot.
Használd az objdump és a GDB eredményét.
(számítási, alapszint — cél: megértés-ellenőrzés) Egy hipotetikus végrehajtás 2 GHz állandó órajel mellett 20 ciklust vesz igénybe. Mennyi az ideje? Miért nem állapítható meg ebből az utasítások száma?
1. megoldás — Segítség. Melyik tulajdonságot garantálja MINDEN azonos ISA-t megvalósító processzor, és melyik változhat gyártónként vagy akár futtatásonként?
1. megoldás — Teljes megoldás. Az A64 utasításhossz és a regiszternézetek írási szabálya ISA-tulajdonság. A gyorsítótár mérete megvalósítási tulajdonság. A falióraidő mérési eredmény, amelyhez a környezetet is rögzíteni kell.
2. megoldás — Segítség. 37-hez mennyit kell adni, hogy 44 legyen? Fér-e ez az érték a 12 bites immediate mezőbe?
2. megoldás — Teljes megoldás. A módosított sor
A64-környezetben: add w0, w1, #7. A 7 belefér a 12 bites
immediate mezőbe. A várt eredmény 44, hexadecimálisan 0x2c;
ezt várjuk a második adatmezőben és kilépési állapotként.
3. megoldás — Segítség. Melyik regiszter tartalmazza
a hibás összeget — az A64 X1 (a betöltött bemenet) vagy az
A64 X0 (az összeadás eredménye)? Innen indulj a
hipotézisalkotásnál.
3. megoldás — Teljes megoldás. Tünet: 42 helyett 43.
Hipotézis: hibás összeadandó. Az objdump mutatja a
tényleges immediate értéket; a GDB-ben az A64 X1 továbbra
is 37, az A64 X0 és a második adatmező viszont 43. Egyetlen
javítás: 6 helyett 5. Újraépítés után mindkét ellenőrzésnek 42-t kell
adnia.
4. megoldás — Segítség. Az órajelciklus-idő ; helyettesítsd be Hz-et, majd szorozd meg 20-szal. A ciklusszám és az utasításszám miért nem ugyanaz a mennyiség, ha a feldolgozás átfedhet?
4. megoldás — Teljes megoldás. Az elméleti idő másodperc, vagyis 10 ns. Az utasításszám nem következik a ciklusszámból, mert a feldolgozás átfedhet, illetve várakozásokat tartalmazhat.
Az ISA azt rögzíti, milyen állapotváltozást kell a programnak megfigyelnie; a mikroarchitektúra azt határozza meg, hogyan jön létre ez az eredmény. A tárolt program utasításai memóriában reprezentált bitminták. A processzor elővételezi, dekódolja és végrehajtja őket, de ezek a fogalmi lépések nem rendelhetők automatikusan külön órajelciklusokhoz. A regiszterek közvetlen operandusokat tartanak, az ALU számol, az összeköttetések adatot továbbítanak. A labor külön betöltést, összeadást és tárolást mutatott, valamint ellenőrizhetővé tette az A64 32 bites regiszterírás nullakiterjesztő hatását. A helyes eredmény önmagában nem mondja meg a hardveres futási időt.
A linkek és a dokumentumverziók közös állapota az Olvasói útmutató forrásállapot-táblájában van; itt csak az alfejezet saját megállapításai szerepelnek.
master ág dokumentációja (közös
verziókorlát). A dokumentáció nem állít semmit a mikroarchitekturális
modellezésről (cache, sávszélesség, ciklusidőzítés) sem.Ellenőrzési hiányok, amelyek a fenti ellenőrzés után is ténylegesen fennmaradnak: laborfuttatás nem történt. A kimenetek levezetett várakozások. A hallgatói laborban rögzítendők az Ubuntu 24.04 környezet tényleges eszközverziói. Mikroarchitekturális késleltetési vagy átbocsátási adatot nem mértünk.