Sebességmérés 1.

Mit lehet ezen magyarázni?

... merülhet fel a kérdés. Nos, a sebességmérő, azaz benchmark rutinok, alkalmazások körül mindig is nagy viták dúltak. Ha annak idején, a 386-osok megjelenésekor a cache-t nem vezették volna be, és ha a processzorok a mai napig megmaradtak volna a Neumann-elvnél, ez a kis iromány biztosan nem jött volna létre. Így azonban nem árt letisztázni a sebességmérés körül kialakult zavarokat. Programozóknak is szolgálnék némi csemegével: benchmark fogások, ötletek és megvalósításaik...

Tesztgép

- Asus P5A-B alaplap 512K sync. pipe burst L2 cache
- AMD-K6-2/400 CPU
- 128 MB PC100 SDRAM
- Matrox Millennium G200 AGP 16 MB SDRAM

  Kinek higgyek?

  Lássuk, mit is mond az ASMDEMO és a WT tesztgépünkre:

  Teszt | Eredmény
  ASMDEMO v1041A - MIPS16 | 374.2
  ASMDEMO v1041A - MIPS32 | 471.1
  ASMDEMO v1041A - FPU24 | 105.8
  ASMDEMO v1041A - FPU64 | 75.1
  ASMDEMO v1041A - MMMIPS | 622.6
  ASMDEMO v1041A - M3DIPS | 486.7
  WT v2.42 - CPU MIPS32 | 645
  WT v2.42 - CPU Cycle Speed | 314.5
  WT v2.42 - FPU_P Instruction Speed | 708
  WT v2.42 - Peak FLOPS | 34.67
  WT v2.42 - 3D Matrix Transf. FLOPS | 22.93
  WT v2.42 - Mandelbrot Generation FLOPS | 5.16
  WT v2.42 - Mandelbrot Generation FLOPS (3DNow!) | 16.58
  WT v2.42 - MMX | 1600

  Jó nagy rumli, az egyszer biztos :) Lássuk sorban! A MIPS, mint rövidítés annyit tesz: Millions of Instructions Per Second. Vagyis másodpercenként végrehajtott millió utasítások száma. Az ASMDEMO CPU ill. FPU sebesség eredményei a MIPS elvet követik, vagyis nem egy viszonylagos teljesítmény indexet, hanem az utasításvégrehajtás konkrét sebességét jelentik.

  A MIPS16 és MIPS32 rutinok a 16 ill. 32 bites, integer (egész) műveleteket használnak, anélkül, hogy bármely processzor fajtára optimalizálva lennének maguk a rutinok. Úgy tűnik, hogy a WT-ben ennek megfelelő eredmények (CPU Cycle Speed és CPU MIPS32) is MIPS-ek, mivel lényegében hasonlóak az ASMDEMO által mért MIPS16/MIPS32 értékekhez.

  A bonyodalmak az FPU-nál kezdődnek. Köztudott, hogy az x86 családba tartozó processzorok a piacvezető Alphákhoz és SPARC-okhoz képest nagyon gyenge FPU-val rendelkeznek. Az x86 procik FPU-jának teljesítménye a MIPS elv szerint csak kb. negyede-fele a CPU egész (integer) egysége sebességének. A K6-nak pedig az x86-os viszonyok között sem túl jó az FPU-ja. A rövidke eszmefuttatásból tehát levonhatjuk a konzekvenciát: a WT által megadott FPU sebességadatok nem lehetnek MIPS elvűek. A 700 túl magas lenne, a 35 pedig kevéske. Bár az eredmények a 'FLOPS', azaz FLoating point Operations Per Second nevet viselik, semmiképpen sem lehetnek a másodpercenként végzett lebegőpontos utasítások darabszámai. A WT Peak FLOPS elnevezésű rutinja elméletileg az FPU másodpercenként maximálisan kibocsátott számítás eredményeit kellene, hogy jelentse. A K6 FPU-ja nagyjából 200 millió műveletet tud másodpercenként elvégezni, ez a 35-höz nem igazán hasonlít (pláne, hogy nem 35 millió, hanem csak simán 35).

  További problémás adatok jönnek: a 3DNow! használatával mindkét program 3-5-szörös sebességnövekedést ért el. Ez sajnos a valóságban nem egészen így van, hiába ezek a szép eredmények. Na, de egyáltalán hogy kell nézni ezeket az adatokat? Mert a 3DNow! bejelentésekor azt is megtudtuk, hogy az új utasításkészlet használatával 500 MHz-en 2 GFLOPS (Giga-FLOPS) peak rate (maximális elméleti sebesség) érhető el. Osszuk le 400 MHz-re: kapunk 1600 MFLOPS-ot (Mega-FLOPS). Hát ez nem igazán hasonlít egyik eredményre sem... Mi lehet a magyarázat? Elsősorban az, hogy egyik rutin sem a maximális elérhető eredmény kibocsátást méri, hanem valódi alkalmazási körülmények között méri a 3DNow! egység teljesítményét. Valamint, az ASMDEMO M3DIPS rutin eredménye a másodpercenként végrehajtott 3DNow! utasítások száma, ami (mivel a 3DNow! SIMD utasításkészlet - azaz egy művelet, több adat) nem egyezik meg a kibocsátott eredmények számával! Egy 3DNow! utasítás maximum 2 eredményt tud előállítani, azaz az M3DIPS értéket 2-vel felszorozva már közelebb juthatunk a valósághoz.

  Az MMX sebesség tesztek sem túl meggyőzőek. Sajnos az MMX-nél igazándiból azt sem lehet tudni, mennyivel kellene a MIPS elvű eredményeket felszorozni, hiszen az MMX utasítások 1, 2, 4 vagy 8 adattal is dolgozhatnak. Az 1600 és a 623 is lehet valós eredmény, csak a programozók tudják, hogy valójában mit is jelentenek ezek az adatok.

  Hiába azonban a csűrés-csavarás, az eredmények még mindig azt tükrözik, hogy a 3DNow! ill. MMX gyorsítások használatával a különböző alkalmazásoknak, játékoknak a sebessége többszörösére kellene, hogy ugorjon...

  Szintetikus benchmark

  Ez tehát a magyarázat. Egészen egyszerűen ha kiragadunk egy bizonyos feladatot, és sokszor egymás után végrehajtjuk (ezen alapszik szinte minden benchmark rutin), akkor a rendszer egy-két bizonyos elemét tudjuk csak munkára fogni, emiatt a rendszer összteljesítményétől, ill. a valódi alkalmazásokban tapasztalható sebességtől nagyon messze lesz az eredmény.

  Általánosan elterjedt az a tévhit, hogy a Linux kernel fordításának (ami nem benchmark célre készült ugye :) mért ideje jól közelíti a rendszer összteljesítményét. Nos, ez részben igaz is, hiszen ha megnézzük, a fordítás egy meglehetősen memória- és processzor igényes folyamat, valamint a háttértárat is szépen munkára fogja egy olyan nagyméretű forráskódnál, mint a Linux kernelje. Azonban ha mélyebbre tekintünk, felfedezhetjük néhány hibáját ennek a 'benchmark eljárás'-nak. A legnagyobb gond, hogy a fordítókód nem igen tartalmaz lebegőpontos műveletet. Így az FPU-t és annak esetleges minden hibáját figyelmen kívül hagyja. Valamint, maga a fordítás, mint művelet nem túlságosan változatos, lévén hogy tele van ellenőrzéssel, elágazással, adatmozgatással, ám nagyon sok utasítás teljesen hiányzik belőle.

  Az igazi benchmark

  Ilyen nem létezik, minden próbálkozás sántít valahol. Ha egy bizonyos komponens sebességét mérjük, akkor az összteljesítményről semmit sem tudunk meg. Ha az utóbbit szeretnénk ábrázolni, akkor fennáll a veszély, hogy a sok használt eszköz közül valamelyik a szűk keresztmetszet miatt degradálja a többi, esetleg nagyobb sebességre is képes komponens teljesítményét. S persze nagyban függ a kapott eredmény használhatósága attól, hogy a rendszer idejének hány százalékát tölti a benchmark rutin által alkalmazott műveletekkel. Hiszen egy hálózati kiszolgáló minden bizonnyal nem fog túl sok Quake-et futtatni, és fordítva: ha a gépünkön soha nem programozunk, valószínűleg nem sok (közvetlen) hasznunk lesz abból, ha a Linux kernel pillanatok alatt lefordul. Mindemellett természetesen egy jól megírt benchmark program eredményeit áttekintve (de nem átlagolva!) viszonylagos képet kaphatunk a vizsgált rendszer teljesítményéről. Valamint, ha a rutinok nem 'részrehajlóak', azaz nincs olyan processzor, mely gyanúsan jól vagy rosszul szerepel a tesztben, akkor természetesen az illető program által szolgáltatott adatok a különböző rendszer komponensek összehasonlítására alkalmasak.

  Ha nem találunk igazán jó programot, tekintsük át, mik a sebességmérés körül fellépő legnagyobb problémák.

  Cache, memória

  A 286-osok idején még minden rendkívül egyszerű volt. Bejött egy utasítás a memóriából, megnézte a proci, mi is az, majd végrehajtotta, előállt az eredmény, és kezdődött minden elölről. Ha az órajel emelésén kívül más trükk nem került volna bevezetésre, a processzor sebességének mérése rendkívül egyszerű feladat lenne még ma is.

  A 386DX idején azonban kitalálták, hogy a processzornak túl sokat kell a lassú memóriára várnia. A memórián és a memória-processzor kapcsolaton gyorsítani - ésszerű és gazdaságos kereteken belül - nem volt lehetőség. Így született meg az alaplapi cache. A gyakran használt memóriatartalmakat a cache tárolja, melynek elérése a központi memóriánál jóval gyorsabb.

  A 486-ban a cache rendszer két szintűre bővült, a processzorba is került cache, melynek elérési ideje még az alaplapi cache-nek is töredéke. Jogos a kérdés: 'Oké, hogy össze-vissza gyorsítjuk a gépet, de miért okoz ez problémát egy benchmark programnak?' Sajnos a cache-ek miatt egyáltalán nem mindegy, hogy mekkora adatmennyiséget mozgatunk a memóriából vagy -ba. Hiszen ha egy rutint úgy tervezünk meg, hogy 20 KB-os adathalmazt kezel, és tele van memória transzferrel, akkor az egy K6-on jóval gyorsabban fog futni, mint egy Pentiumon. A K6-nak ugyanis a 32 KB elsőszintű adat cache-ébe (data L1 cache) simán befér a 20 KB-os blokk, míg a Pentium 8 KB-os hasonló tára ehhez nem elegendő. De még ha figyelünk is arra, hogy semmi szín alatt ne vegye a rutin figyelembe a prociban található cache-t, nem lehetünk biztosak afelől, hogy a jövőben mekkora cache-sel készülnek a 7. ill. 8. generációs CPU-k (a K7 már most összesen 128 KB L1 cache-t tartalmaz).

  A cache tehát az (egyik) oka annak, hogy nagyon sok régi benchmark program irtózatos sebességet mér a mostani gépeken - már ha egyáltalán lefut a rutin, nem akad ki a végén esetleg egy nullával való osztással :)

  A memóriával is vannak gondok, ez részben a cache-nek tudható be: nem mindegy, hogy a memória mely címéről olvasunk ill. címre írunk. A változók ún. igazítása nagyon fontos: ha nem 8-cal osztható memória címet használunk, akkor a legtöbb 6. generációs processzornál tetemes időveszteséget szenvedhet a rutinunk. Emiatt előfordulhat, hogy egy egyébként nagyon gyors processzoron is meglepően lassan fut egy alkalmazás - hiszen az itt felsorolt problémák nem csak a benchmark programokat érintik!

  Spekulatív végrehajtás

  A modern processzorok felépítése rendkívül bonyolult, működésük pedig majdhogynem felfoghatatlan. Egy funkcióra több egység is lehet, valamint egy egység több funkciót is elláthat. Ezen kívül, a különböző feladatok egymással párhuzamosan is végrehajthatóak, sőt: akár a lineárisan egymás után következő műveletek sorrendje is felcserélődhet!

  A sok kis trükköt összefoglalva spekulatív végrehajtásnak szokták nevezni. Miért 'spekulatív' ? Mert a processzor megpróbálja lehetőség szerint számára optimálissá tenni a feladatot. Így az 5. és 6. generációs x86 processzorok már a Neumann-elvre csak néhány pontban emlékeztetnek, és sajnos a régebbi CPU családoknál megszokott, jól bevált sebességmérő módszerek alapos felülvizsgálatra szorulnak.

  Lássuk sorban a spekulatív végrehajtás során felmerülő buktatókat...

  Ha több azonos típusú utasításra (pl. egész aritmetikai vagy logikai műveletek) külön feldolgozó egység áll rendelkezésre, akkor az utasítások külön kiküldhetőek a két egységhez, vagyis az első utasítás megy az egyik végrehajtó egységhez, a másik megy a másikhoz. Azonban ha a két utasítás által használt erőforrások (regiszterek, memóriatartomány) megegyeznek, vagyis a műveletek között függés áll fenn, akkor a processzornak el kell döntenie, ki tudja-e valami úton-módon ezt küszöbölni. Mivel a processzorok utasítás végrehajtási mechanizmusának vannak jól megfogalmazható törvényszerűségei, a programkód megtervezhető olyanra, hogy a függések száma minimális legyen. Sajnos ezek a szabályok CPU családonként változóak, így egy P6/PII/PIII-ra megírt kód nem futtatható optimálisan egy K6-on - és viszont. Tehát nem mindegy, hogy a benchmark rutinunkat mely processzorra optimalizáljuk, és nem árt tisztában lenni a többi proci viselkedésével sem, hiszen előfordulhat olyan eset is, hogy egy pl. Pentiumra optimalizált kód K6-on olyannyira lassan fut, hogy az eredmény távolról sem fog emlékeztetni a processzor tapasztalati úton megállapított átlagos teljesítményére...

  A függéseken kívül nagy gondot okozhat még a 6. és 7. generációs processzorok hosszú pipeline-ja is. A probléma főleg akkor jelentkezik, ha egy kód nagyon szaggatott, azaz tele van ide-oda ugrálásokkal (elágazásokkal). Bár a Pentium óta minden processzor végez elágazás becslést, azonban ennek hatékonysága csak akkor érezhető, ha hosszú ciklusok, gyakran ismétlődő azonos irányú elágazások követik egymást a kódban. A CASE (többirányú) elágazások, a rekurzív rutinok és a JMP [ESI] -hez hasonló ugrások esetén a becslésnek alig van jelentősége. Ha pedig a becslés nem jön be, a hosszú pipeline-t a proci eldobja, és amíg újra feltölti, a rendszer várakozni kényszerül. Az elágazások elkerülésére vezették be a CMOV utasítást, mely feltételtől függően végez memória ill. regiszter transzfert (a kód ezáltal nem törik meg, hanem lineáris marad).

  A kitüntetett utasítások, ill. adatok is okozhatnak gondot. Mit is kell érteni ez alatt? Nem mindegy pl., hogy milyen operandusokkal végzünk el pl. egy szorzást. A 8 hatványai, a 0 és az 1 a modern CPU-k általában kitüntetten kezelik, ilyen elemekkel a műveletek elvégzése gyorsabban történhet. Ezért az a legbiztosabb, hogy ha mindig gyök kettővel szorzunk vagy osztunk :) Kitüntetett utasításoknál előfordul, hogy a processzor átlép, vagy éppen 0 idő alatt hajt végre egy kódrészletet - ebből adódhat aztán a Clipper programoknál már megszokott 'Divison by zero' hiba...

  A P6/PII/PIII architektúra specialitása az eddig említetteken felül, hogy a 16 bites kódokat sokkal lassabban hajtja végre, mint az ugyanarra a célra íródott 32 bites verziót. A teljesítmény veszteség akár 50 % is lehet!

  SCSI, EIDE

  Talán kevésbé ismert az a sajnálatos tény, hogy az EIDE busz forgalma a CPU-t is terheli. Tipikus esetben ez a processzor akár 20-30 %-át is 'megeheti', ami jó néhány alkalmazás sebességét döntően befolyásolhatja. Ez a probléma a jóval drágább SCSI eszközök esetén nem áll fenn. Ez tehát a magyarázat arra, hogy miért is nem szeretnek a nagy szerverekben EIDE buszt használni - leterhelt processzor esetén a winchester forgalma elvenné az időt a rendszeren futó alkalmazásoktól.

  3D gyorsítók

  A mai divatnak hódolva ejtsünk néhány szót róluk is. A jelenlegi 3D gyorsítók (TNT2, G400, Voodoo3) legnagyobbjai sem képesek minden felbontásban maximális teljesítményre a leggyorsabb PC processzorokkal (Athlon-600, PIII-Xeon-550) sem. Vigyázni kell tehát arra, hogy a 3D sebesség mérésekor tisztán lehessen látni a szűk keresztmetszetet. Ha a 3D gyorsító kezd kifulladni, a felbontás növelésével a teljesítmény szépen, fokozatosan csökken. Ha azonban a processzor (pontosabban az FPU) nem elég gyors a 3D geometria kiszámításához, akkor a felbontás emelésével (egy bizonyos felbontás küszöbig) a sebesség görbéje vízszintes lesz.

  Hamarosan megérkeznek az első mindenki számára megfizethető, geometriai gyorsítással is felvértezett 3D chipkészletek. Ezeknél már nem lesz döntő az FPU sebessége - de természetesen a játékírók fel fogják használni a 'feleslegessé' váló teljesítményt...

  ... to be continued ...

  A 2. részben megismerjük a PC-s benchmark programozás fortélyait, néhány mindenki számára érhető és használható rutinnal fűszerezve - melynek elméleti alapjait is tisztázzuk...

  Fiery
  fieryofup@writeme.com