Pomnilniška hierarhija1
V splošnem programi v nekem trenutku ne dostopajo do poljubnih podatkov ali izvajajo poljubne kode. Namesto tega sledijo načelu lokalnosti, kar pomeni, da večinoma dostopajo do razmeroma majhnega in lokaliziranega dela svojega naslovnega prostora. Obstajata dve različni vrsti lokalnosti:
- časovna lokalnost in
- prostorska lokalnost.
Časovna lokalnost predpostavlja, da če dostopamo do neke lokacije s podatki ali ukazi, je zelo verjetno, da bomo do nje v bližnji prihodnosti ponovno dostopali. Prostorska lokalnost predpostavlja, da če dostopamo do neke lokacije, bomo zelo verjetno dostopali tudi do sosednjih lokacij. Sodobni računalniki s ciljem izboljšanja zmogljivosti uporabljajo pomnilniško hierarhijo vedno manjših in vedno hitrejših pomnilnikov. Tak pristop je mogoče uporabiti zaradi načela lokalnosti, sicer bi bil neuporaben. Pomnilniško hierarhijo je sestavljena iz več ravni pomnilnika z različnimi zakasnitvami, prepustnostmi in velikostmi. Pomnilniški model GPE združuje ločene pomnilniške sisteme gostitelja in naprave ter izpostavlja celotno pomnilniško hierarhijo, tako da lahko izrecno nadzorujemo razporeditev podatkov za doseganje čim višje zmogljivosti. Pomnilniški model GPE izpostavlja naslednje vrste pomnilnika:
- registri,
- lokalni pomnilnik,
- skupni pomnilnik,
- pomnilnik konstant,
- pomnilnik tekstur in
- globalni pomnilnik.
Na spodnji sliki je predstavljena pomnilniška hierarhija CUDA-podprtih GPE:
Registri
Registri so zelo pomemben vir pomnilniškega prostora in predstavljajo najhitrejši pomnilniški prostor na GPE. Neka spremenljivka, deklarirana v kodi za GPE, je običajno shranjena v registru. Registri so lastni vsaki niti, kar pomeni, da registri, dodeljeni posamezni niti, niso vidni drugim nitim. O uporabi registrov odloča prevajalnik. Vsaka računska enota ima 64k 32-bitnih registrov (arhitektura Volta), ki si jih bloki niti, ki se hkrati izvajajo na njej, delijo. Spomnite se, da je omejitev 64 sočasnih snopov (enakovredno 2048 nitim), ki se lahko izvajajo na dani računski enoti. V primeru, da prevajalnik dovoli posamezni niti uporabljati več kot 32 registrov, se s tem zmanjša največje število blokov niti, ki se lahko izvajajo izvajajo na računski enoti, kar lahko poslabša zmogljivost. Nasprotno lahko uporaba manjšega števila registrov omogoči, da se na računski enoti nahaja več blokov niti hkrati. To lahko poveča izkoriščenost in izboljša zmogljivost. Prevajalnik se, če ni na voljo dovolj registrov, lahko odloči in nitim za hranjenje vrendosti spremenljivk dodeli prostor v lokalnem pomnilniku.
Lokalni pomnilnik
Lokalni pomnilnik je viden samo znotraj posamezne niti. Ostale niti ne morejo dostopati do njega. Podatke tja postavi prevajalnik, če se izkaže, da za posamezno nit ni na voljo dovolj registrov ali ugotovi, da določene podatkovne strukture ni smiselno oziroma ni mogoče hraniti v registrih, ker je prevelika. To lahko negativno vpliva na hitrost izvajanja, saj se lokalni pomnilnik fizično nahaja v globalnem pomnilniku in imajo dostopi do njega posledično zelo visoko zakasnitev.
L1/skupni pomnilnik
Vsaka računska enota vsebuje hiter pomnilnik, ki se lahko uporablja kot predpomnilnik L1 ali skupni pomnilnik. Vse niti v bloku si delijo skupni pomnilnik. Vsi bloki, ki tečejo na dani računski enoti, pa si delijo fizični pomnilniški vir, ki ga zagotavlja računska enota. Ker je skupni pomnilnik na čipu, ima veliko večjo prepustnost in veliko manjšo zakasnitev kot globalni pomnilnik. Vsaka računska enota ima omejeno količino skupnega pomnilnika, ki je razdeljen med bloke niti. Zato morate biti previdni, da skupnega pomnilnika ne izkoristite preveč, sicer boste nenamerno omejili število aktivnih snopov. Skupni pomnilnik si življenjsko dobo deli z blokom niti. Ko blok niti konča izvajanje, se njegova dodelitev skupnega pomnilnika sprosti in dodeli drugim blokom. Najpomembnejša funkcija skupnega pomnilnika je, da služi kot sredstvo za komunikacijo med nitmi. Niti znotraj bloka lahko sodelujejo tako, da si delijo podatke, shranjene v skupnem pomnilniku.
Na primer, Nvidia Tesla V100 ima 128 kB pomnilnika L1/Shared memory, ki ga je mogoče konfigurirati na šest različnih načinov:
- 32 kB L1 in 96 kB skupnega pomnilnika;
- 64 kB L1 in 64 kB skupnega pomnilnika;
- 96 kB L1 in 32 kB skupnega pomnilnika;
- 112 kB L1 in 16 kB skupnega pomnilnika.
- 120 kB L1 in 8 kB skupnega pomnilnika.
- 128 kB L1 in 0 kB skupnega pomnilnika.
Bralni pomnilnik
Vsaka računska enota ima ukazni predpomnilnik, pomnilnik konstant, pomnilnik tekstur in bralni predpomnilnik, ki je namenjen samo za branje.
Pomnilnik konstant se nahaja v globalnem pomnilniku in je predpomnjen v namenskem predpomnilniku konstant. Pomnilnik konstant omogoča, da en podatek razpošljemo vsem nitim hkrati (angl. broadcasting). Pomnilnik tekstur se nahaja v globalnem pomnilniku in se predpomni v namenskem predpomnilniku na računski enoti. Pomnilnik tekstur je optimiziran za dostope do 2D podatkovnih struktur; z njegovo uporabo lahko pohitrimo dostop do konstantnih tekstur in slik.
Predpomnilnik L2
Predpomnilnik L2 je skupen vsem SM, zato lahko do tega njega dostopajo vse niti. Na primer, grafični procesor Nvidia Tesla V100 ima predpomnilnik L2 velikosti 6144 kB. Predpomnilnik L2 ni viden skozi programski pomnilniški model. GPE z njim upravlja samodejno, glede na vzorce pomnilniških dostopov, ki jih opravljajo niti.
Globalni pomnilnik
Globalni pomnilnik je ekvivalent glavnemu pomnilniku centralne procesne enote; tu se nahajajo programi in vsi podatki. Globalni pomnilnik je največji, najpočasnejši in najpogosteje uporabljen pomnilnik v grafičnem pospeševalniku. Ime globalni se nanaša na njegov obseg in življenjsko dobo. Do podatkov v globalnem pomnilniku lahko dostopamo iz katere koli računske enote v celotnem življenjskem ciklu aplikacije. CPE lahko bere in piše iz/v globalni pomnilnik. Takšni prenosi podatkov potekajo prek vodila PCIe in so razmeroma počasni, zato moramo programe, ki izkoriščajo GPE oblikovati tako, da karseda zmanjšamo komunikacijo med CPE in GPE. Podatki se v globalnem pomnilniku GPE ohranjajo tekom celotne življenske dobe aplikacije, tako da jih je mogoče večkrat uporabiti, ne da bi jih bilo treba ponovno naložiti. Globalni pomnilnik je izveden v hitrem pomnilniku GDDR, ki dosega zelo veliko prepustnost, vendar ima tako kot vsi pomnilniki DRAM visoko zakasnitev.
Dostopi do globalnega pomnilnika iz računske enote so vedno široki 128 bajtov, začnejo pa se na naslovu, poravnanem s 128 bajti. Pomnilniški bloki, do katerih se dostopa v eni pomnilniški transakciji, se imenujejo segmenti. To ima izjemno pomembno posledico. Če dve niti iz istega snopa dostopata do dveh podatkov, ki spadata v isti 128-bajtni segment, se podatki dostavijo v eni transakciji. Če so v segmentu podatki, ki jih ni zahtevala nobena nit, se ti vseeno preberejo - s tem se zmanjša efektivna prepustnost. Če dve niti iz istega snopa dostopata do dveh podatkov, ki spadata v različna 128-bajtna segmenta, se prav tako izvedeta dve pomnilniški transakciji. Zelo pomembno je, da že pri nitenju naših programov zagotavljamo združevanje pomnilniških dostopov v segmente (angl. memory coalescing) - trudimo se, da vse niti v snopu dostopajo do zaporednih podatkov v pomnilniku.
Programerjev pogled na pomnilniško hierarhijo GPE
Niti med izvajanjem dostopajo do podatkov iz več pomnilniških prostorov, kot je prikazano na spodnji sliki. Vse niti, zagnane na GPE predstavljajo mrežo niti (angl. grid). Vsaka nit ima zasebne registre ter lokalni pomnilnik. Vsak blok niti ima skupni pomnilnik, ki je dosegljiv vsem nitim v bloku in ima enako življenjsko dobo kot blok. Vse niti imajo dostop do istega globalnega pomnilnika. Obstajata tudi dva dodatna pomnilniška prostora, namenjena samo branju, do katerih imajo dostop vse niti: pomnilniška prostora za konstante in teksture. Življenska doba globalnega, konstantntnega in teksturnega pomnilniškega prostora je enaka življenski dobi aplikacije.
-
© Patricio Bulić, Davor Sluga, Univerza v Ljubljani, Fakulteta za računalništvo in informatiko. Gradivo je objavljeno pod licenco Creative Commons Priznanje avtorstva-Nekomercialno-Deljenje pod enakimi pogoji 4.0 Mednarodna. ↩