Hyper-threading aneb “Jak sakra může běžet víc vláken na jednom jádře?”

Nedávno jsem narazil na článek, který testoval, jak se pod zátěží chová procesor se zapnutým hyper-threadingem. Autor onoho textu na základě měření a vlastních předpokladů vyslovoval divoké domněnky a spekulace, které bohužel neměly příliš mnoho společného s chladnou realitou křemíku.

Otázka tedy zní: Jak je to vlastně s hyper-threadingem a simultaneous multithreading (SMT) obecně? Jak můžou běžet dvě vlákna na jednom procesorovém jádře zároveň?

Odpověď je jednoduchá: SMT se stará jenom o to, aby procesor měl stálý přísun nezávislých instrukcí k vykonání na out-of-order jádru.

Téměř všechna moderní CPU jsou pipelinované out-of-order superskalární procesory1, které dokážou dělat mnoho věcí najednou. Nejde jen o souběžná vlákna běžící na různých jádrech multiprocesoru, ale také o schopnost naráz vykonávat několik instrukcí jednovláknového programu (jde o takzvaný instuction level parallelism, zkráceně ILP). Tohle je možné právě proto, že i když se současné procesory navenek tváří, jako kdyby plně respektovaly Von Neumannovu architekturu, jejich vnitřní fungování v mnohém připomíná data-flow model. Tento způsob práce se označuje jako out-of-order execution (OOO). CPU nezpracovává jednu instrukci za druhou, ale načítá a dekóduje2 jich několik v každém taktu. Tyto instrukce jsou poté umístěny do re-order bufferu (ROB), což je ve své podstatě fronta dekódovaných mikrooperací3, které čekají na vykonání. Out-of-order jádro se poté snaží dynamicky tyto instrukce provést na dostupných funkčních jednotkách procesoru (nejnovější Intely, které je označují jako porty, jich mají osm) a zkouší vybrat co nejvíce instrukcí, které mohou běžet najednou. Když jsou operace nezávislé (tj. nezávisí na žádných předchozích) mohou být vykonány paralelně na různých portech. To je obzvláště výhodné, když jde o load4 instrukce načítající data z paměti. Než blok dat dorazí z RAM do cache CPU, může to trvat až několik stovek taktů. Pokud OOO engine dokáže zařídit, aby nezávislé load operace běžely paralelně, může tak dramaticky zrychlit program5. Pokud ale instrukce A závisí na B, A musí počkat ve frontě re-order bufferu dokud B není plně uskutečněna a teprve poté dostane svojí nanosekundu slávy. Moderní procesory vynakládají velké úsilí, plochu a energii na to, aby zaručily, že ve frontě jsou pořád nějaké nezávislé instrukce připravené k vykonání – prefetchují data, odhadují podmíněné skoky a divoce spekulují, jen aby objevily tu další load instrukci a narazily na ten další cache-miss.

Bohužel ILP má své limity a v běžných programech je jen omezené množství nezávislých instrukcí. Ivan Godard z Mill Computing říká „Tajemství out-of-order procesorů je, jak málo out-of-order vlastně jsou“6". Každé jádro Haswellu je schopné načíst, dekódovat a vykonat 4 instrukce v každém taktu8, mají re-order buffer délky 19212 a krotí smečku 168 virtuálních general purpose registrů7, ale jen zřídka dokáže tyto prostředky naplno využít. Běžný kód zkrátka neobsahuje dost ILP na to, aby byl procesor plně vytížený. Některé zdroje uvádějí, že běžné maximum ILP je někde kolem 2.5.

Právě v tomto místě přichází ke slovu hyper-threading. Když máme velice paralelní procesory, které většinu času nejsou schopny zcela využít svůj potenciál, je jenom logické před jádro přidat druhý frontend, který načítá a dekóduje instrukce jiného vlákna a který zásobí out-of-order strojovnu novým proudem instrukcí, v němž je s trochou štěstí možné najít dostatečné množství nezávislých operací, které zužitkují kapacitu dostupného hardwaru.

Taková je podstata hyper-threadingu a SMT9. Nejde o hardwarové context-switche10, nejde o to, že se Von Neumannovský procesor maskuje za dva (jako je tomu v případě barell temporal multithreading nebo super-threading), jde jen o to využít všechny možnosti procesoru, které by jinak ležely ladem.11

Nevýhodou hyper-threadingu je skutečnost, že najednou běží víc nezávislých vláken, které sdílejí dostupnou cache. Každé hyper-vlákno má tedy k dispozici efektivně poloviční množství cache, než kdyby procesor neměl povolený HT. To může v některých případech mít značný vliv na rychlost programu. I tady platí staré rčení: „Když jsi s rozumem v koncích, použij perf“.


Dále k tématu:


  • 1) Prvním komerčně úspěšným OOO procesorem bylo Pentium Pro. Průkopníky v této oblasti však byli gangsteři v kravatách z IBM, kteří navrhli první OOO procesor už v šedesátých letech.
  • 2) To že dekódování x86 instrukcí je problém se můžete přesvědčit tady a tady. Současné generace CPU mají kromě instrukční level 1 cache, také μops cache a loop buffer proto, aby eliminovaly režii dekódování instrukcí, které mají prefixy, escape-sekvence a proměnnou délku.
  • 3) Instrukce jsou dekódovány na mikrooperace (μops), které jsou pak vykonány procesorem. Proto i CISC architektury jako x86 se uvnitř chovají jako RISC.
  • 4) Na x86 jde o mov instrukci, která má jako zdroj adresu paměti.
  • 5) Zrychlení je tak dramatické, že všechny optimalizace v procesoru směřují jen a pouze k tomuto cíli – víc souběžných cache-miss (viz).
  • 6) Zdroj se mi nepodařilo vypátrat, ale je to zmíněno někde v sérii přednášek pořádaných Mill Computing. Godard dost možná cituje někoho jiného, pravděpodobně z týmu vyvíjejícího Itanium.
  • 7) A stejný počet floating point virtuálních registrů. Virtuální registry určují jak dalece může jádro spekulovat.
  • 8) Technicky je možné až 6 instrukcí, když program má to štěstí, že procesor dokáže sloučit několik párů operací (macro-op fusion). Čistě teoreticky by mohlo jít až o 8 operací vykonaných na všech osmi portech. Poslední generace procesorů Itanium (Poulson) stabilně zvládá 12 instrukcí za takt (ale v tomto případě nejde o OOO, ale o open pipeline a static scheduling).
  • 9) SMT bývá mnohem divočejší v ne-x86 procesorech. Například Power8 nebo Sparc M7 dokáže provádět osm vláken najednou, nikoli pouhé dvě, jak je běžné ve světě Intelu a AMD. Jde o kompromis mezi agresivitou a paralelismem procesoru. x86 se očividně snaží optimalizovat jednovláknový výkon, kdežto Power a Sparc se svými osmi souběžnými vlákny míří na propustnost a throughput.
  • 10) Hardwarové context-switche a hardwarový scheduling jsou však běžné ve světě GPGPU.
  • 11) Některý, zvláště numerický kód dokáže z procesoru vymáčknout 4 instrukce za takt (IPC). V takovém případě HT nebo SMT nemá žádný pozitivní dopad na výkon.
  • 12) Velikost ROB ve své podstatě udává jak daleko do budoucnosti procesor vidí. Čím dál vidí, tím víc ILP může z kódu získat. Někdy však může nastat divoká souhra okolností, která má překvapivě negativní dopad na výkon, jak je demonstrováno v tomto videu.

Flattr this!

This entry was posted in CPU, Paměť. Bookmark the permalink.

One Response to Hyper-threading aneb “Jak sakra může běžet víc vláken na jednom jádře?”

  1. peter tutor says:

    kniha Java Concurrency in Practice od Brian-a Goetz-a, mi svojho casu otvorila oci, ze rozmyslat v threadoch je dost ale dost zlozite
    skoda, ze v reale nie je vela firiem kde sa daju taketo veci riesit na projektoch

Leave a Reply

Your email address will not be published. Required fields are marked *