No x86 on HUONO tätä nykyä.
Ei ole. x86-64 on toiseksi paras yleisesti käytetty CPU-käskykanta, heti ARMv8n jälkeen.
Se on parempi kuin esim. SPARC, MIPS, PPC, Itanium, RISC-V tai Alpha.
Jotain modernisaatiota kaipaa kun nykynen ISA on suunniteltu 16 bittisille laitteille ja on 40 vuotta vanha, johon lätkitty vaan lisää päälle.
Sitä on modernisoitu vaikka kuinka moneen kertaan:
80386 poisti pakon käyttää segmenttejä mihinkään ja toi modernin ja järkevän muistinhallintayksikön.
P6 toi ehdolliset käskyt (vähensi selvästi haarautumisten tarvetta) sekä laajensi fyysiset osoitteet 36-bittisiksi.
P4 toi SSE2n jonka avulla ei ollut mitään tarvetta enää käyttää x87-FPUsta joka oli hirveä sotku.
K8 laajensi kokonaislukudatapolun 64 bittiin, virtuaaluosoitteet 48 bittin (optiolla laajentaa ne täyteen 64 bittiin asti) ja poisti 64-bittisessä tilassa viimeisetkin jäljet segmenteistä. Ja samalla osa alkuperäisistä opkoodeista uudelleen.
Ja kuin kirsikaksi kruunuun se 16 bittinen ISA oli suunniteltu lähdekoodi yhteensopivaksi 50 vuotta vanhan 8-bit vermeille tarkoitetun ISAn päälle.
Tällä ei ole mitään merkitystä. Se, että joillekin rekistereille määritellään samat aliakset assemblyyn ei haittaa yhtään mitään.
RISCeillä duunailleena tuntuu nykyään aika tympeältä kun x86 käskyt ei olekkaan ihan niin standardi mittasia vaan siellä voi olla se 1 byte (nop) ja sitten aika helvetin pitkiin olikos nyt 15 tavua pitkiin hirvityksiin
CISCin iloja.
"tuntuu tympeältä" arvo teknisenä argumentina on puhdas 0.
Keskimääräinen käskynpituus x86lla on lyhempi kuin suurimmalla osalla RISCejä, ja jos verrataan muihin kuin ARMv8iin, saman asian tekemiseen tarvitaan myös vähemmän käskyjä =>
koodin koko samalle asialle selvästi pienempi, ja tällä saadaan mm. parempi käskyvälimuistien osumatarkkuus.
Ja niitä 15 tavun käskyjä ei käytännössä koskaan näe missään. Se tarkoittanee sitä, että siellä on kaikki erilaiset prefix-tavut ja sitten jostian pitkästä käskystä versiot joissa on pari 64-bittistä immediate -arvoa. Saman asian tekevät RISC-koodi muuten vaatii PALJON ENEMMÄN tavuja, kun esim useimmissa RISCeissä pelkästään YHDEN 64 bitin immediaten siirtämiseenkin tarvitaan 128 bittiä=16 tavua käskyjä.
Ja x86ssa on vaikka kuinka paljon HYÖDYLLISIÄ JÄRKEVÄÄ TYÖTÄ TEKEVIÄ yhden tavun käskyjä. 4 kertaa lyhyempänä kuin useimmissa RISCeissä. Yhden tavcun ksäkyt ei yleensä ole mitään NOPeja, toki on olemaassa myös yhden tavun NOP.
Se, että käskyt on hankalampi dekoodata tuntuu pikkusen, esim siinä että järeimmät x86-prossut dekoodaa vain 4-5 käskyä kellojaksossa (ja yksi vähemän järeä 2*3), ja käskyn dekoodaukseen tarvii pari liukuhihnavaihetta. Ja siihen, että saadaan prossu etupäässä käsiteltyä enemmän kuin 4-5 käskyä kellojaksossa on silti ratkaisu olemassa, mikro-op-välimuisti, joka tallettaa dekoodattuja käskyjä, löytyy Inteliltä P4sta sekä Core-sarjasta Sandy Bridgestä lähtien, ja AMDltä Zenistä lähtien.
Se mikro-op-välimuisti maksaa inasen pinta-alaa, mutta pii-pinta-ala on nykyään älyttömän halpaa. Ja se 1-2 vaihetta pidempi decode-vaihe tuntuu inasen haarautumisenennustushudissa, mutta se, onko se 16,17 vai 18 kellojaksoa ei ole kovin merkityksellistä, kun haarautumisenennustus on kuitenkin todella hyvä, ja huti on joka tapauksessa paha hidastus.
Enempi alkaa olemaan rajoitteena jo sekin että siinä missä modernimmissä vermeissä on enempi yleiskäyttösiä rekisterejä niin niiden lukumäärä laahaa x86_64:ssa perässä.
Rekisterien uudelleeniomeäminen on keksitty, ja x86-käskyt voivat lukea operandinsa suoraan muistista. Mitään merkittävää pullonkaulaa rekisterimäärän suhteen ei x86-
64ssa ole.
Nykyisissä x86-prossuissa on ~180 yleiskäyttöistä rekisteriä.
Sitä en tiedä miksi aikanaan ei x86_64sen kanssa hypätty suurempaa GPR lukumäärään.
Jospa ottaisit selvää, mistä puhut.
x86-64ssa yleiskäyttöisten kokonaislukurekisterien määrä nimenomaan nostettiin kahdeksasta kuuteentoista, koska se 8086n/80386n 8 oli jossain määrin pullonkaula.
Ja syyt miksei sitä lisätty enempä on selvät ja hyvät:
Arkkitehtuurilliset rekisterit näkyy käskyenkoodauksessa. x86-käskyt voivat olla paljon lyhempiä (todella monet yleiset käskyt on 2 tavua, REX-prefixin kanssa 3, tyypillisillä RISCeillä 4 tavua) koska rekistereitä on vähän ja kohderekisterin pitää useimmilla käskyillä olla sama kuin toisen lähderekisterin. Rekisterien tuplaus 8->16 maksoi jo tuon REX-prefix-tavun. Rekisterien määrän laajennus 32 bittiin olisi käytännössä maksanut varmaan toisen tavun, ja poistanut täysin x86n käskyntiheysylivoiman.
Nyt siis x86n koodintiheysetu kapeni selvästi näiden REX-tavujen takia.
Ja hyöty 32 arkkitehtuurillisesta rekisteristä hyvin, hyvin pieni.
Rekistereitä vaatii paljon lähinnä sellaisiin optimointeihin, joita spekulatiivisella out-of-order-prosessorilla ei tarvitse tehdä.
Mutta, tuo tapa miten tuo reksiterien määrän lisäys jouduttiin tekemään tuolla REX-prefixillä on käytännössä melkein AINOA historiapainolasti jota x86-64lla on jäljellä; Jos itse käskykannan olisi pitänyt samana mutta käskyenkoodauksen olisi voinut laittaa täysin uusiksi, samat käskyt olisi menneet keskimäärin hiukan pienempään tilaan, jolloin x86n etumatka käskyenkoodauksen tiheydessä ei olisi kaventunut kuten se nyt kapeni.
Näitä ongelmia ei valmistusprosessi korjaa :/
Nämä eivät ole mitään ongelmia.
Jospa puhuttaisiin niistä muiden arkkitehtuurien oikeista/suuremmista ongelmista:
* MIPSissä ja SPARCissa on branch delay slot.
Hypyn jälkeinen käsky suoritetaan aina, otettiin hyppy tai ei. Tällä saatiin poistettua stalli 5-vaiheiseltaä liukuhihnalta 35 vuotta sitten.. Nykyisssä prossiossa on n. 12-18-vaiheisia liukuhihnoja ja ne fetchaa 4-8 käskyä kellojaksossa. Delay slotteja tarvittaisiin n. 71-95 kpl että niillä voitaisiion ratkaista tämä ongelma mitä delay sloteilla silloin ratkaistiin => nykyään käytetään haarautumisenennustusta haarautumisenkohdepuskurin kanssa. Se, että meillä on se yksi delay slotti siellä tekee vaan kaikesta hankalampaa, ja välillä pakottaa kääntäjän myös tunkemaan sinne ylimääräisen NOPin.
* MIPSissä on erilliset lo- ja hi-rekisterit kerto- ja jakolaskulle.
Näiden takia kerto- ja jakolaskujen kanssa tarvitaan hirveä ylimääärinen härväys datan siirtämiseksi näihiin tai näistä pois. Vähän samaa ongelmaa kuin mitä oli x87.FPUssa (jota ei enää 18 vuoteen ole tarvinnut käyttää)
* SPARCissa ja Itaniumissa on rekisteri-ikkunat.
Nämä tekevät rekisterien uudelleennimeämisestä paljon vaikeampaa, ja tämän takia Sun teki ensimmäisen käskyjä uudelleenjärjestelevän SPARC-suorittimen n. 16 vuotta muita valmistajia myöhemmin. Ja käskyjen uudelleenjärjestely rekisterien uudelleennimeämisellä on käytännössä elinehto hyvälle tai edes välttävälle yhden säikeen suorituskyvylle tällä vuosituhannella.
* MIPSissä, RISC-Vssä ja Alphassa ei ole muuta muistinosoitussmuotoa kuin [reg+imm].
Eli jos halutaan vaikak tehdän normaalia taulukon käyttämistä, tarvitaan siihen KOLME käskyä (indeksin skaalaus datatyypin kokoiseksi, pointterin ja skaalatun indeksin yhteenlasku, ja iutse muistioiperaatio). x86 ja ARM tekee tämän yhdellä käskyillä. Joissain muissa RISCeissä on [reg+reg] eli sujuu kahdella käskyllä, parempi kuin MIPS, RISC-V ja Alpha, mutta silti huonompi kuin ARM ja x86.
* RISC-V:stä puuttuu kokonaan kaikki muut ehdolliset käskyt kuin hypyt.
Koodiin tarvitaan selvästi enemmän haarautumisia, joka pikkuprossulla tarkoittaa enemmän stalleja, tehokkaalla prossulla tarkoittaa sitä, että haarautumisenennustusyksikön kirjanpidosta loppuu tila aiemmin kesken ja sen osumatarkkuus on huonompi. (eli lopulta myös enemmän stalleja). Ja siinä ei ole ollenkaan nopeaa keinoa suorittaa koodia, jossa on haarautumisia, joita on mahdoton ennustaa, esim hakuja binäärihakupuusta; Tällöin se joutuu aina haarautumaan ja failaamaan ennustuksen (josta seuraa pitkä paha stall ja haaskattua virtaa) 50% tapauksista, siinä missä muilla arkkitehtuureilla voidaan ehdollisilla siirroilla odottaa vaan pari kellojaksoa sitä ehdon laskentaa.
* PPCssä on hyvin monimutkaiset käänteiset hajautustauluun perustuvat sivutaulut, joiden käsittely käyttöjärjestelmän toimesta on todella hankalaa ja hidasta, ja niiden lisäksi käyttis joutuu vielä toteuttamaan omaa kirjanpitoaan jota normaalien hierarkisten risvutaulujen tapauksessa ei tarvita (koska suorana prossun käytössä olevat sivutaulut toimii suoraan myös käyttiksen omana kirjanpitona). Tämä tekee esim. uusien prosessien luonnista sekä prosessien vaihtamisesta PPCllä hitaampaa ja on vaikempaa kirjotitaa käyttiskoodi joka tekee nämä bugittomasti.
* PPC on natiivisti big-endian ja sen lisäksi myös kutsuu suurinta bittiä indeksillä 0, pienintä bittiä indeksillä N-1 missä N on prosessorin bittimääärä. Eli alin bitti on jokko bitti 31 tai bitti 63 riippuen prossun toimintatilasta, samoin se bitti mikä 32-bittisenä on ylin bitti on joko bitti 0 tai bitti 32 riippuen prossun toimintatilasta. Melkoista sillisalaattia.
* Itaniumissa kolme käskyä vie yhteensä 16 tavua tilaa, ja sen lisäksi koodiin tarvitana NOPeja => itanium-koodi vie TODELLA PALJON tilaa => Suuri muistintarve käskyille, käskyvälimuistin huono osumatarkkuus.
* RISC-V:stä puuttuu täysin järkevät "keskiraskaat" SIMD-laajennokset.
Sille on määritelty P-laajennos, joka vain mahdollistaa skaalarireksiterien pätkimisen pienemmiksi laskutoimituksisksi, mutta tämä tarkoittaa sitä että vektorin leveys on 32-bittisellä prossulla 32 bittiä ( 8 kertaa kapeampi kuin AVX) ja 64-bittisellä prossulla 64 bittiä (4 kertaa kapeampi kuin AVX). Näillä ei suurta suorituskykyä saada.
Lisäksi sille ollaan määrittelemässä V-laajennusta joka on ominaisuuksiltaan paljon "parempi," mutta se ihan hirveä overnengineerattu häröpallo, joka ei todennäköisesti monimutkaisuutensa takia tule saamaan kunnollista kääntäjätukea n. 10 vuoteen, eikä sitä varmaan implementoidakaan käytännössä mihinkään koska sen implementointikin on niin hankalaa.
* Alphasta ja SPARCista puuttuu myös kaikki "keskiraskaat" ja järeämmät SIMD-laajennokset. Niissä on vain RISC-V:n P-extension tapainen normaaleita (kapeita) skalaarirekisterejä käyttävä, jolla suoerituskykyä ei saa paljoa.
* Kaikilla muilla arkkitehtuureilla paitsi x86lla ja SPARCilla on löysemmät muistin konsistenttiussäännöt. Koodiin tarvii lisätä ylimääräisiä fence-käskyjä, joiden yli muisitoperaatioita ei saa uudelleenjärjestellä, jos halutaan, että muistioperaatiot näkyy toisille säikeille samassa järjestyksessä. X86 voi kuitenkin todellisuudessa uudelleenjärjestellä muistioperaatiota, kunhan vain pitää kirjaa niiden alkuperäisestä järjestyksestä ja sekä huolehtii järjestyksen näennäisestä säilymisestä säikeen sisällä (mm tarvittaessa forwardoiden arvoja store-puskurista loadeille) että välittää muutokset toisille säikeille konsistenttiussääntöjen mukaisessa järjestyksessä.
Mikäli muista, löysemmät konsistenttiussäännöt omaavista arkkitehtuureista tehdään muistioperaatioita yhtä aggressiivisesti uudelleenjärjesteleviä versiota, ne joutuvat kuitenkin pitämään kirjaa operaatioiden alkuperäisestä järjestyksestä sekä tekemään saman store-to-load-forwardingin yhden säikeen sisällä. Tällöin niistä löysemmistä konsistenttiussäännöistä ei enää hyödytäkään mitään, kun ne kuitenkin feikataan, ja jäljelle jää lähinnä overhead niistä fence-ksäkyistä (jotka pakottaa flushailemaan paljon muutakin kuin oikeasti tarvisi)
* ARMv8lla, SPARCilla, PPCllä ja Alphalla kaikki käskyt on 32 bittiä pitkiä. Erityisesti SPARCilla ja PPCllä tämä tarkoittaa huonohkoä käskytiheyttä => tarvitaan paljon käskymuistia, huonompi osumatarkkuus käskyvalimuistille.
ARMv8ssa on sama ominaisuus, mutta siinä on niin paljon ilmaisuvoimaisempia käskyjä, että se tarvii vähemmän käskyjä saman asian tekemiseen, että siinä tämä ei ole merkittävä huonous, sen koodin tiheys ei silti ole huono. PPC on myös jossain määrin ilmaisuvoimaisempi kuin SPARC eli oikeastaan pahana jää jäljelle vain SPARC:
RISC-V:ssä, MIPSissä ja ARMv7ssa sitten myös 16-bittinen käskyenkoodaus eniten käytetyille käskyille, ja ARMv7/Thumb2lla päästää jopa 386sta tiheämpään koodiin ja MIPS16 ja RISCV-C pääsee lähelle 386sta ja välillä jopa voittaa x86-64n.
* Alphassa ei ole hardware-page walkeria. Mikäli osoitteenmuunnosdataa ei löydy TLBstä, seuraa poikkeus joka lentää PAL-koodilla olevaan käsittelijään, joka lataa sivutaulun hitaasti softalla. Tämä on paljon hitaampaa kuin sivutaulujen hakeminen raudalla.
* MIPS, SPARC, Itanium, Alpha eivät tue alifgnoimattomia muistiaccesseja. RISC-V tukee, mutta ainakin toinen kahdesta yleisimmästä kääntäjästä sille kieltäytyy tekemästä niitä, lähinnä koska niiltä vaaditaan tukea vain user-moodissa ja kääntäjän halutaan toimivan myös kernelille.
Näillä arkkitehtuureilla, jos kääntäjä ei pysty todistamaan, että (muuten laillinen) muistiaccess on alignoitu, se luo hirveän häröpallokoodin jossa muuttaa (mahdollisesti) alignoimattoman muistiaccessin moneksi alignoiduksi muistiaccessiksi. Ja tämä häröpallokoodi on HIDAS.
Ja tosiaan, vaikka itse access olisi oikeatit alignoitu, mutta kääntäjä ei vaan tiedä sitä, tämä häröpallokoodi tehdään.
Arkkitehtuureilla, joissa unaligned accessit on aina tuettuja(ja kääntäjä tietää sen), kääntäjä ei tee mitään häröpallokoodia, ja se muistiaccess saattaa olla esim. yhden kellojakson hitaampi JOS se OIKEASTI sattuu olemaan unaligned.
x86-64lla on käytännössä jäljellä kaksi epäoptimaalisuutta:
* Se käskyenkoodaus on turhan monimutkainen eikä optimaalinen. Tästä oli puhetta jo aiemmin.
* Tuettuna ei ole mitään virtuaalimuistisivujen kokoa 4 kiB ja 2 MiB välistä. Nykyaikana joku 16k-64 k olisi usein järkevämpi, 2 megaa on aivan liian suuri käytettäväksi "kaikkialla", 4 kiB yleisesti käytetty virtuaalimuistisivu tarkoittaa sitä, että L1D-kakun maksimi-way-koko on 4 kiB että voidaan tehdä VIPT-tyyppinen L1D-kakku ilman mitään aliasoitumista (joka on hirveä suo). Eli 8 wayllä L1D-kakun maksimikoko on 32 kiB(zen, sandy bridge-skylake), sunny covessa sitten 12-way 48 kiB. Applella on ARMv8ssaan 16 kiB välimuistisivut joilla voidaan VIPT-tyyppisenä tehdä 4 kertaa suurempi L1D-kakku ilman että way-määrä kasvaa suuremmaksi, ja viimeisissä Applen ytimissä L1D-kakku onkin 128 kiB.