6.5. Formă și pași (strides)¶
Datele din interiorul unui ndarray sunt un singur bloc compact de numere. Descriptorul din fața acelui bloc decide cum este citit acel bloc plat ca un tensor.
6.5.1. Ce înregistrează descriptorul¶
Cinci valori descriu cum se citește blocul de date ca un tensor:
a = np.array([[1, 2, 3], [4, 5, 6]], dtype=np.uint8)
a.ndim # 2 - number of dimensions
a.shape # (2, 3)- length along each dimension
a.itemsize # 1 - bytes per element (from dtype)
a.size # 6 - total number of elements
a.strides # (3, 1)- step pattern through the buffer
Ajutorul ndinfo() le afișează pe toate, plus locația tamponului subiacent, într-un singur apel. Două tablouri ale căror locații de tampon coincid partajează memoria:
np.ndinfo(a)
# class: ndarray
# shape: (2, 3)
# strides: (3, 1)
# itemsize: 1
# data pointer: 0x...
# type: uint8
6.5.2. Pașii (strides) explicați¶
Un pas (stride) este numărul de octeți cu care trebuie să avansezi în blocul de date pentru a te deplasa cu un element de-a lungul unei axe date. Pentru tabloul uint8 2x3 de mai sus, pașii sunt (3, 1): deplasarea în jos cu un rând sare 3 octeți, deplasarea la dreapta cu o coloană sare 1 octet. Aceasta este echivalent cu a spune că rândurile sunt stocate spate în spate, de la stânga la dreapta:
memory: [ 1 ][ 2 ][ 3 ][ 4 ][ 5 ][ 6 ]
^ row 0 ^ row 1
<------- 3 bytes ---->
Pentru a citi a[i, j], numpy calculează i * strides[0] + j * strides[1] de la începutul blocului de date și citește itemsize octeți de acolo. Aceeași formulă se extinde la orice număr de dimensiuni.
Această dispunere – rânduri stocate cap la cap, cu ultima axă variind cel mai rapid în memorie – se numește ordine row-major. Fiecare tablou pe care numpy îl alocă pe cameră folosește această dispunere.
6.5.3. Ordinea row-major are consecințe¶
Două lucruri decurg din „rânduri stocate spate în spate” care contează atunci când dai formă unui tampon pe cameră.
Ultima axă este contiguă. Parcurgerea de la a[0, 0] la a[0, 1] atinge octetul următor. Parcurgerea de la a[0, 0] la a[1, 0] sare peste un rând întreg.
Ultima axă este axa rapidă pentru calculele pe întregul tablou. numpy pe cameră parcurge întotdeauna ultima axă cel mai în interior, indiferent care axă se întâmplă să fie mai lungă. Biblioteca numpy de desktop își reordonează în mod silențios buclele pentru a pune cea mai lungă axă cel mai în interior; camera nu o face, așa că o alegere de dispunere pe care numpy de desktop ar fi mascat-o costă în continuare timp aici. np.sum(m, axis=1) colapsează ultima axă și rulează în direcția contiguă; np.sum(m, axis=0) nu o face. Atunci când aplicația poate alege cum să dispună un tampon, pune axa lungă ultima, astfel încât operațiile de-a lungul ei să rămână în bucla interioară.
Dacă dispunerea pornește greșit, transpose() (sau scurtătura .T) o corectează fără a copia datele – doar schimbă pașii (strides) între ei:
a = b.T # now iterates fast
Performanță conține discuția completă despre performanță.
6.5.4. Reshape, transpunere, segmentare – editări de descriptor¶
Orice operație care doar rescrie descriptorul este gratuită. reshape schimbă o nouă shape și noi strides peste același bloc de date. transpose inversează pașii. a[::2] dublează un pas. Fiecare returnează o vedere a aceluiași tampon subiacent.
Orice operație care trebuie să parcurgă datele și să scrie un tampon nou este o copie. Regula deocamdată este că editările de descriptor sunt gratuite, iar parcurgerile de date nu sunt.
6.5.5. O notă despre ndim¶
numpy pe cameră este construit cu un ndim maxim suportat de 4. Operațiile care ar produce un tablou de rang superior ridică ValueError. Marea majoritate a lucrului pe partea de cameră este 1-D sau 2-D, așa că limita este rareori o problemă.