superscalar und einem vliw prozessor



  • hi,
    was ist der unterschied zwischen einen superscalar und einem vliw prozessor, abgesehen das man beim superscalar zur laufzeit bestimmt, welche befehle in welcher funktionseinheit landen, was beim vliw prozessor zur compilezeit passiert?
    was ist besser?

    bye



  • folgendes verstehe ich nicht;/
    - ein superskalarer Prozessor speist seine Ausführungseinheiten aus nur einem Befehlsstrom einfacher Befehle,
    - bei einem VLIW-Prozessor ein Befehlsstrom von VLIW-Befehlspaketen, also von Tupeln einfacher Befehle



  • Wie definierst Besser? Auf jeden Fall haben sich VLIW-Prozessoren nicht durchgesetzt, wie der Intel Itanium.



  • naja besser kommt wohl aufs anwendungsgebiet an;)

    was heisst folgendes?
    - ein superskalarer Prozessor speist seine Ausführungseinheiten aus nur einem Befehlsstrom einfacher Befehle,

    - bei einem VLIW-Prozessor ein Befehlsstrom von VLIW-Befehlspaketen, also von Tupeln einfacher Befehle
    all diese einfachen befehle im tupel werden parallel ausgeführt oder werden da tupel parallel ausgeführt? wo ist der unterschied zu superskalar?

    cu



  • Superscalar: Komplexität wird auf den Prozessor übertragen (Prozessor verwaltet Parallelisierung zur Laufzeit)

    VLIW: Komplexität wird auf den Compiler übertragen (Compiler verwaltet Parallelisierung zur Compilezeit)

    Was ist einfacher? Frage einen Compilerentwickler und einen Hardwareentwickler und du wirst 2 verschiedene Antworten bekommen. Was ist zur Laufzeit effizienter? Potenziell würde ich sagen VLIW, da zur Laufzeit weniger "Schalten" notwendig ist. Letzten Endes sollte es aber hauptsächlich auf die Implementierung ankommen.



  • superman1 schrieb:

    naja besser kommt wohl aufs anwendungsgebiet an;)

    was heisst folgendes?
    - ein superskalarer Prozessor speist seine Ausführungseinheiten aus nur einem Befehlsstrom einfacher Befehle,

    - bei einem VLIW-Prozessor ein Befehlsstrom von VLIW-Befehlspaketen, also von Tupeln einfacher Befehle
    all diese einfachen befehle im tupel werden parallel ausgeführt oder werden da tupel parallel ausgeführt? wo ist der unterschied zu superskalar?

    das bedeutet, dass beim superskalar prozessor der ganze code befehl fuer befehl konsumiert wird, beim vliw wird in bloecken . was besser ist, ist wie du schon gesagt hast, anwendungsgebiet abhaengig. das ist wie cisc vs risc.

    fuer ganz simplen code ist cisc+superscalar+outoforder am besten, weil der prozessor quasi generischen opcode schlucken muss und entsprechend der einheiten und ihren spezifischen latencies und throughputs und auch abhaengig von externen einfluessen (z.b. ram latenz oder cache), die einheiten am flexibelsten nutzen kann.

    wenn du hand optimierst bzw dir genau ansiehst was der compiler macht auf der untersten ebene, dann wird risc interesant. es gibt viele kleine dinge die viel angenehmeren code fuer den prozessor generieren, aber niemand schaut nach um das zu sehen und es ist auch inpraktikabel das fuer den ganzen code zu machen. ein beispiel

    //copy
    u16* pIn...
    u16* pOut...
    for(int a=0;a<N;a++)
      pOut[a]=pIn[a];
    

    der gcc generierste code in pseudo assembler (jede c zeile ist ein assembler befehler quasi, nur verstaendlicher) fuer den ARM ist dann in etwa

    a=0
    m:
     InAddresse=pIn+a
     OutAddresse=pOut+a
     InAddresse*=2
     OutAddresse*=2
     kopiere ein word in TempRegister aus  [OutAddresse] 
     kopieren ein word in [InAddresse] aus Tempregister
     a++
     vergleiche a gegen 100
     falls kleiner, branche/springe zu zeile m
    

    bevor ich jetzt alles in details ausfuehre und dich zutode langweile schreibe ich es mal die finale version (sorry fuer eventuelle fehler, ist sicher schon 5jahre her dass ich das letzte mal fuer ARM schrieb)

    u8* pIn=(u8*)...
    u8* pOut=(u8*)...
    u8* pOutEnd=pOut+200
    
    do
      *(u16*)pOut=*(u16*)pIn;
      pOut++;
      pInt++;
    whie(pOut!=pOutEnd)
    

    in der output ist dann

    pOut
    m:
     kopiere ein word in TempRegister aus  [OutAddresse] ; OutAddresse+=2;
     kopieren ein word in [InAddresse] aus Tempregister ; InAddresse+=2;
     vergleiche pOut gegen pOutEnd
     falls kleiner, branche/springe zu zeile m
    

    wie du siehst, kann der ARM in einem befehl dinge umkopieren und den index um die groesse des elements das man kopiert incrementieren, aber simple addressberechnung waere dann fatal.
    Du siehst,dein code kann 2 bis 3mal schneller werden indem du deinen c code umstellst nachdem du dir den assembler anschaust und das gilt fuer risc.

    nun mal VLIW, das ist sozusagen multi-unit risc. du hast in einem opcode strang n-streange "versteckt". ich nehme mal als beispiel hardware eine SPU, das ist simple VLIW mit 2 straengen (auch wenn es dem "very long" entgegen steht 😉 )
    der decoder nimmt also immer 64bit (32bit pro befehl) und steckt sie in die ausfuehrungseinheiten. dabei muss er 1. sie immer in der richtigen reihenfolge ausfuehren und 2. sind die befehle immer 32bit aligned.
    die 2 pipelines werden "odd" und "even" genannt und koennen ca die haelfte der befehle jeweils, wobei jeder befehl exklusiv pro pipeline ist. eine genaue auflistung findest du hier
    machen wir es simpel und sagen, die linke kann rechnen und logik (ala or) und die rechte kann load/store und branching.
    hast du z.b. sowas wie

    load add load load add add store compare branch
    

    im decoder kommt folgendes an

    load add 
    load load 
    add add 
    store compare 
    branch ..
    

    auf die einheiten verteilt also

    ___  load 
    add  ___
    ___  load 
    ___  load 
    add  ___
    add  ___
    ___  store 
    ___  compare 
    ___  branch
    

    du siehst, massive gaps in der pipe, nun wagen wir mal ein experiment und fuegen etwas unsinniges ein

    nop load add load load add add store compare branch
    

    im decoder kommt folgendes an

    nop load 
    add load 
    load add 
    add store
    compare branch
    

    auf die einheiten verteilt also

    nop load 
    add load 
    ___ load 
    add ____
    add store
    ___ compare 
    ___ branch
    

    du siehst, es ist nicht zwingend intuitiv was man machen muss um eine instruktion schneller zu werden, du siehst aber auch, x-instruktionen spaeter hat das noch einfluss und paired add und store, aber genau so kann es sein dass es solche instruktionen un-paired. der compiler muss also neben dem generieren von moeglichst effektivem code auch noch
    1. die packete effektiv zusammenstellen wobei jedes packet auch potentiell die nachfolgenden beeinflusst
    2. register usage nicht nur zwischen den einzelnen instructions, sondern auch zwischen den packeten tracken und optimieren
    3. beim zusammenstellen von packeten die latencies der einzelen befehle beachten in bezug auf die vorherigen packete und befehle und die nachfolgenden packete und befehle
    ...
    und jetzt nimm noch branching dazu etc.

    und um jetzt kein buch zu schreiben, mal kurz was ein static code analyser sagt wenn man 08/15 c code compiliert

    251  0x00000b10    and     $5,$6,$78        0x00000b14    lnop    
     252  0x00000b18    il      $6,128           0x00000b1c    shufb   $9,$3,$8,$60
     253  0x00000b20    ila     $8,0x10203       0x00000b24    lqd     $3,256($127)
     254
     255
     256                                         0x00000b28    stqx    $9,$11,$38
     257                                         0x00000b2c    lqx     $10,$80,$39
     258                                         0x00000b30    lqx     $9,$11,$39
     259
     260
     261
     262
     263                                         0x00000b34    rotqby  $87,$10,$88
     264
     265
     266
     267                                         0x00000b38    shufb   $88,$87,$9,$59
     268
     269
     270
     271                                         0x00000b3c    stqx    $88,$11,$39
     272                                         0x00000b40    lqx     $2,$80,$40
     273                                         0x00000b44    lqx     $10,$11,$40
     274
     275
     276
     277
     278                                         0x00000b48    rotqby  $38,$2,$89
     279
     280
     281
    

    wie du siehst, benutzt der compiler moeglichst viele register um register presure nicht beim optimieren beachten zu muessen, und versucht mit nops (erste zeile) die pipeline gut zu fuellen, aber einfach aufgrund der art und weise wie der code geschrieben wurde gibt es eine extreme abhaengigkeit der einzelnen befehle und dann kann der compiler nicht sonderlich viel machen ohne sich auf gefaehrlichen boden zu bewegen.
    wenn man dann noch die latenz der einzelnen befehle sieht, sieht man dass man sie auch locker haette nacheinander ausfuehren koennen ohne dass etwas grossartig ineffizienter geworden waere. (und ich habe mir keinen besonderen code rausgesucht, sondern einfach mal runtergescrollt und was kopiert).

    und mit hand optimierten code nach ner woche arbeit
    -inlining von allem was da ist
    -eliminierung jeglichen if/else
    -spu instrinsics
    -vectorisierung

    55  0x00001bd0    il      $83,12           0x00001bd4    rotqmby $7,$32,$60
      56  0x00001bd8    fm      $33,$59,$3       0x00001bdc    shlqby  $10,$29,$124
      57  0x00001be0    il      $32,4            0x00001be4    shlqby  $28,$23,$17
      58  0x00001be8    sfi     $124,$27,0       0x00001bec    rotqmby $6,$14,$122
      59  0x00001bf0    ai      $23,$58,4        0x00001bf4    shlqby  $18,$105,$27
      60  0x00001bf8    or      $93,$4,$10       0x00001bfc    shufb   $35,$113,$24,$98
      61  0x00001c00    or      $45,$7,$28       0x00001c04    shufb   $68,$19,$19,$51
      62  0x00001c08    il      $105,20          0x00001c0c    shufb   $81,$114,$20,$98
      63  0x00001c10    or      $44,$6,$18       0x00001c14    shufb   $80,$112,$115,$98
      64  0x00001c18    il      $114,28          0x00001c1c    rotqbyi $125,$87,8
      65  0x00001c20    fma     $88,$68,$35,$33  0x00001c24    shufb   $15,$93,$93,$51
      66  0x00001c28    il      $113,24          0x00001c2c    shufb   $30,$45,$45,$51
      67  0x00001c30    ai      $28,$58,8        0x00001c34    shufb   $31,$44,$44,$51
      68  0x00001c38    ai      $68,$58,20       0x00001c3c    rotqbyi $79,$93,4
      69  0x00001c40    fm      $37,$15,$3       0x00001c44    rotqbyi $86,$45,4
      70  0x00001c48    fm      $85,$30,$3       0x00001c4c    shufb   $96,$81,$80,$8
      71  0x00001c50    fm      $34,$31,$3       0x00001c54    shufb   $39,$125,$125,$51
      72  0x00001c58    ai      $31,$58,12       0x00001c5c    rotqbyi $123,$44,4
      73  0x00001c60    il      $125,40          0x00001c64    rotqbyi $63,$87,12
      74  0x00001c68    nop     $127             0x00001c6c    shufb   $82,$79,$79,$51
      75  0x00001c70    fma     $50,$39,$96,$88  0x00001c74    shufb   $65,$86,$86,$51
      76  0x00001c78    ai      $86,$58,36       0x00001c7c    shufb   $36,$123,$123,$51
      77  0x00001c80    il      $123,36          0x00001c84    rotqbyi $38,$93,8
      78  0x00001c88    fma     $91,$82,$35,$37  0x00001c8c    rotqbyi $40,$45,8
      79  0x00001c90    fma     $47,$65,$35,$85  0x00001c94    shufb   $57,$81,$80,$98
      80  0x00001c98    fma     $46,$36,$35,$34  0x00001c9c    shufb   $100,$63,$63,$51
      81  0x00001ca0    ai      $34,$58,24       0x00001ca4    rotqbyi $69,$44,8
      82  0x00001ca8    il      $65,52           0x00001cac    shufb   $43,$38,$38,$51
      83  0x00001cb0    ai      $38,$58,28       0x00001cb4    shufb   $48,$40,$40,$51
      84  0x00001cb8    fma     $55,$100,$57,$50 0x00001cbc    lqd     $54,1104($127)
      85  0x00001cc0    ai      $100,$58,60      0x00001cc4    shufb   $94,$69,$69,$51
      86  0x00001cc8    fma     $53,$43,$96,$91  0x00001ccc    rotqbyi $49,$93,12
    

    der code wurde ca 50mal schneller insgesammt. manches kann man dem compilier nicht abgewoehnen und mit hand geschriebenem spu assembler bekommt man noch ca 10% mehr raus.

    wenn man VLIW code sieht mit 6 pipes die alle was unterschiedliches machen, ist die ausbeute meistens sehr arm und der compiler kann wirklich nicht viel machen, selbst bei super geschriebenem code. ich habe hier fuer manche dinge compiler die 6minuten an ca 100 opcodes werkeln und dann trotzdem kein optimales resultat liefern. Ich habe mit compilerschreiben geredet die nichts anderes als VLIW compiler jahrelang schrieben und die meinen sie moegen VLIW nicht wirklich, aus dem simplen grund weil die optimierung so complex von sovielen knappen resourcen abhaengen, dass es nichtmal simple ist einen static code analyser zu schreiben und dafuer dann noch zu optimieren ist ab nem gewissen grad eher ein for(a=0 to 100){randomize_instructions;static_profile;save_if_better;}
    und so mancher code der rauskommt ist erstmal unerklaerlich und man involviert die halbe support firma bis dann jemand ne erklaerung hat wieso der compiler etwas macht wie er es macht und die chancen sind ca 50:50 dass er es schlauer gemacht hat als es jeder mensch sieht oder dass es ein weiterer sonderfall ist den man dem compiler beibringen muss.

    ich hoffe meine kleine exkusrion hat alle klarheiten beseitigt 🙂



  • hi,

    ein noch paar fragen zum vliw (very long instruction word) processor:
    - was heisst "explicite parallelism"?
    - kurze, unabhängige befehle werden zu einem langen befehlswort = 1 maschinenbefehl zusammengepackt.
    werden dann alle befehle synchron abgearbeitet?
    - um n befehle in einem befehlspaket gleichzeitig abzuarbeiten brauch ich n funktionseinheiten oder?
    - warum hat der vliw eine einfacherer hardware als der superscalar proc? weil der superscalar die zuweisung der befehle zur laufzeit machen muss?

    lg



  • superman1 schrieb:

    - was heisst "explicite parallelism"?

    weil

    werden dann alle befehle synchron abgearbeitet?

    - um n befehle in einem befehlspaket gleichzeitig abzuarbeiten brauch ich n funktionseinheiten oder?

    so sollte es sein

    - warum hat der vliw eine einfacherer hardware als der superscalar proc? weil der superscalar die zuweisung der befehle zur laufzeit machen muss?

    zuweisung ist nicht das problem, VLIW weist ja auch zu.
    decodieren und verteilen ist aufwendiger, gerade wenn die instruktionsgroesse dynamisch ist. dafuer werden n-bytes gelesen und an jeder bytestelle wird spekulativ dekodiert und am ende vom decoder wird dann erst selektiert welche der ganzen dekodierten befehle die richtige sequenz haben. beim in order werden die befehle dann auf die einzelnen pipelines gelegt und zum richtigen zeitpunkt, wie bei VLIW zugewiesen.

    jedoch sind die decoder, obwohl sie komplex klingen, meist ein sehr kleiner teil der prozessoren. daran aendert sich meist auch wenig und die auslastung ist bei kritischen applikationen auch eher gering weil dort die befehle im post-decode buffer meist fuer die kritischen schleifen reichen.
    entsprechend haben nahalem CPUs als stromspar mittel die moeglichkeit den decoder kurzzeitig abzuschalten. (wenn ich es richtig gelesen habe).



  • - was heisst "explicite parallelism"?
    weil

    was meinst du mit weil?



  • "explicit parallelism" heißt, dass bei VLIW im Befehlscode, der vom Compiler erstellt wird, explizit die parallel abzuarbeitenden Befehle angegeben sind.



  • Bei dynamisch superskalaren Prozessoren stehen keine Parallelisierungsinformationen im Befehlscode. Deshalb nutzt beispielsweise die x86-Architektur das auch. Obwohl erst später Superskalareigenschaften hinzugefügt wurden, ist der Befehlscode trotzdem zu nicht-superskalaren Prozessoren der gleichen Architektur abwärtskompatibel, und es kann alter Code weiterhin verwendet werden.


Anmelden zum Antworten