funkcionálně.cz

Přední český blog o funkcionálním programování, kde se o funkcionálním programování nepíše
««« »»»

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

21. 1. 2015 — k47

Ne­dávno jsem na­ra­zil na článek, který tes­to­val, jak se pod zátěží chová pro­ce­sor se za­pnu­tým hyper-threa­din­gem. Autor onoho textu na zá­kladě měření a vlast­ních před­po­kladů vy­slo­vo­val divoké do­mněnky a spe­ku­lace, které bo­hu­žel neměly příliš mnoho spo­leč­ného s chlad­nou re­a­li­tou kře­míku.

Otázka tedy zní: Jak je to vlastně s hyper-threa­din­gem a si­mul­ta­ne­ous mul­ti­threa­ding (SMT) obecně? Jak můžou běžet dvě vlákna na jednom pro­ce­so­ro­vém jádře zá­ro­veň?

Od­po­věď je jed­no­du­chá: SMT se stará jenom o to, aby pro­ce­sor měl stálý přísun ne­zá­vis­lých in­strukcí k vy­ko­nání na out-of-order jádru.

Téměř všechna mo­derní CPU jsou pi­pe­li­no­vané out-of-order su­per­ska­lární pro­ce­sory1, které do­ká­žou dělat mnoho věcí na­jed­nou. Nejde jen o sou­běžná vlákna běžící na růz­ných já­drech mul­ti­pro­ce­soru, ale také o schop­nost naráz vy­ko­ná­vat ně­ko­lik in­strukcí jed­no­vlák­no­vého pro­gramu (jde o tak­zvaný in­stuction level pa­ral­le­lism, zkrá­ceně ILP). Tohle je možné právě proto, že i když se sou­časné pro­ce­sory na­ve­nek tváří, jako kdyby plně re­spek­to­valy Von Ne­u­man­novu ar­chi­tek­turu, jejich vnitřní fun­go­vání v mnohém při­po­míná data-flow model. Tento způsob práce se ozna­čuje jako out-of-order execu­tion (OOO). CPU ne­zpra­co­vává jednu in­strukci za druhou, ale načítá a de­kó­duje2 jich ně­ko­lik v každém taktu. Tyto in­strukce jsou poté umís­těny do re-order bu­f­feru (ROB), což je ve své pod­statě fronta de­kó­do­va­ných mi­k­ro­o­pe­rací3, které čekají na vy­ko­nání. Out-of-order jádro se poté snaží dy­na­micky tyto in­strukce pro­vést na do­stup­ných funkč­ních jed­not­kách pro­ce­soru (nej­no­vější Intely, které je ozna­čují jako porty, jich mají osm) a zkouší vybrat co nej­více in­strukcí, které mohou běžet na­jed­nou. Když jsou ope­race ne­zá­vislé (tj. ne­zá­visí na žád­ných před­cho­zích) mohou být vy­ko­nány pa­ra­lelně na růz­ných por­tech. To je ob­zvláště vý­hodné, když jde o load4 in­strukce na­čí­ta­jící data z paměti. Než blok dat dorazí z RAM do cache CPU, může to trvat až ně­ko­lik stovek taktů. Pokud OOO engine dokáže za­ří­dit, aby ne­zá­vislé load ope­race běžely pa­ra­lelně, může tak dra­ma­ticky zrych­lit pro­gram5. Pokud ale in­strukce A závisí na B, A musí počkat ve frontě re-order bu­f­feru dokud B není plně usku­teč­něna a teprve poté do­stane svojí na­no­sekundu slávy. Mo­derní pro­ce­sory vy­na­klá­dají velké úsilí, plochu a ener­gii na to, aby za­ru­čily, že ve frontě jsou pořád nějaké ne­zá­vislé in­strukce při­pra­vené k vy­ko­nání – pre­fet­chují data, od­ha­dují pod­mí­něné skoky a divoce spe­ku­lují, jen aby ob­je­vily tu další load in­strukci a na­ra­zily na ten další cache-miss.

Bo­hu­žel ILP má své limity a v běž­ných pro­gra­mech je jen ome­zené množ­ství ne­zá­vis­lých in­strukcí. Ivan Godard z Mill Com­pu­ting říká „Ta­jem­ství out-of-order pro­ce­sorů je, jak málo out-of-order vlastně jsou“6". Každé jádro Haswellu je schopné načíst, de­kó­do­vat a vy­ko­nat 4 in­strukce v každém taktu8, mají re-order buffer délky 19212 a krotí smečku 168 vir­tu­ál­ních ge­ne­ral pur­pose re­gis­trů7, ale jen zřídka dokáže tyto pro­středky naplno využít. Běžný kód zkrátka ne­ob­sa­huje dost ILP na to, aby byl pro­ce­sor plně vy­tí­žený. Ně­které zdroje uvá­dějí, že běžné ma­xi­mum ILP je někde kolem 2.5.

Právě v tomto místě při­chází ke slovu hyper-threa­ding. Když máme velice pa­ra­lelní pro­ce­sory, které vět­šinu času nejsou schopny zcela využít svůj po­ten­ciál, je jenom lo­gické před jádro přidat druhý fron­tend, který načítá a de­kó­duje in­strukce jiného vlákna a který zásobí out-of-order stro­jovnu novým prou­dem in­strukcí, v němž je s tro­chou štěstí možné najít do­sta­tečné množ­ství ne­zá­vis­lých ope­rací, které zužit­kují ka­pa­citu do­stup­ného hard­waru.

Taková je pod­stata hyper-threa­dingu a SMT9. Nejde o hard­wa­rové con­text-switche10, nejde o to, že se Von Ne­u­man­nov­ský pro­ce­sor mas­kuje za dva (jako je tomu v pří­padě barell tem­po­ral mul­ti­threa­ding nebo super-threa­ding), jde jen o to využít všechny mož­nosti pro­ce­soru, které by jinak ležely ladem.11

Ne­vý­ho­dou hyper-threa­dingu je sku­teč­nost, že na­jed­nou běží víc ne­zá­vis­lých vláken, které sdí­lejí do­stup­nou cache. Každé hyper-vlákno má tedy k dis­po­zici efek­tivně po­lo­viční množ­ství cache, než kdyby pro­ce­sor neměl po­vo­lený HT. To může v ně­kte­rých pří­pa­dech mít značný vliv na rych­lost pro­gramu. I tady platí staré rčení: „Když jsi s ro­zu­mem v kon­cích, použij perf“.


Dále k tématu:


@kaja47, kaja47@k47.cz, deadbeef.k47.cz, starší články