6.5. Form och strides¶
Datan inuti en ndarray är ett packat block med tal. Deskriptorn framför det blocket avgör hur det platta blocket läses ut som en tensor.
6.5.1. Vad deskriptorn registrerar¶
Fem värden beskriver hur datablocket ska läsas som en 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
Hjälpfunktionen ndinfo() skriver ut alla dem plus platsen för den underliggande bufferten i ett enda anrop. Två arrayer vars buffertplatser matchar delar minne:
np.ndinfo(a)
# class: ndarray
# shape: (2, 3)
# strides: (3, 1)
# itemsize: 1
# data pointer: 0x...
# type: uint8
6.5.2. Strides förklarade¶
En stride är hur många byte man ska stega i datablocket för att flytta sig ett element längs en given axel. För 2x3-uint8-arrayen ovan är strides (3, 1): att flytta nedåt en rad hoppar 3 byte, att flytta höger en kolumn hoppar 1 byte. Det är samma sak som att säga att raderna lagras rygg mot rygg, från vänster till höger:
memory: [ 1 ][ 2 ][ 3 ][ 4 ][ 5 ][ 6 ]
^ row 0 ^ row 1
<------- 3 bytes ---->
För att läsa a[i, j] beräknar numpy i * strides[0] + j * strides[1] från början av datablocket och läser itemsize byte därifrån. Samma formel utökas till valfritt antal dimensioner.
Denna layout – rader lagrade ände mot ände, med den sista axeln som varierar snabbast genom minnet – kallas radmajor-ordning. Varje array som numpy allokerar på kameran använder denna layout.
6.5.3. Radmajor har konsekvenser¶
Två saker följer av ”rader lagrade rygg mot rygg” som spelar roll när man formar en buffert på kameran.
Den sista axeln är sammanhängande. Att gå från a[0, 0] till a[0, 1] rör vid nästa byte intill. Att gå från a[0, 0] till a[1, 0] hoppar över en hel rad.
Den sista axeln är den snabba axeln för matematik på hela arrayen. numpy på kameran går alltid längs den sista axeln innerst, oavsett vilken axel som råkar vara längre. Skrivbordsbiblioteket numpy ordnar tyst om sina loopar för att placera den längsta axeln innerst; kameran gör inte det, så ett layoutval som skrivbords-numpy skulle ha dolt kostar fortfarande tid här. np.sum(m, axis=1) kollapsar den sista axeln och körs i den sammanhängande riktningen; np.sum(m, axis=0) gör det inte. När applikationen har ett val om hur en buffert ska läggas ut, placera den långa axeln sist så att operationer längs den stannar i den inre loopen.
Om layouten börjar fel åtgärdar transpose() (eller genvägen .T) det utan att kopiera datan – den byter bara strides:
a = b.T # now iterates fast
Prestanda har den fullständiga prestandadiskussionen.
6.5.4. Reshape, transpose, slicing – deskriptorredigeringar¶
Varje operation som bara skriver om deskriptorn är gratis. reshape byter en ny shape och strides över samma datablock. transpose vänder på strides. a[::2] fördubblar en stride. Var och en returnerar en vy av samma underliggande buffert.
Allt som måste gå igenom datan och skriva en ny buffert är en kopia. Regeln för tillfället är att deskriptorredigeringar är gratis och datagenomgångar inte är det.
6.5.5. En anmärkning om ndim¶
numpy på kameran är byggt med ett maximalt stödd ndim på 4. Operationer som skulle producera en array med högre rang ger upphov till ValueError. Den stora majoriteten av arbetet på kamerasidan är 1-D eller 2-D, så gränsen är sällan ett problem.