6.5. Forma e strides

I dati all’interno di un ndarray sono un unico blocco impacchettato di numeri. Il descrittore davanti a quel blocco decide come quel blocco piatto viene letto come un tensore.

6.5.1. Cosa registra il descrittore

Cinque valori descrivono come leggere il blocco di dati come un tensore:

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

L’helper ndinfo() li stampa tutti, piu la posizione del buffer sottostante, in un’unica chiamata. Due array le cui posizioni di buffer coincidono stanno condividendo la memoria:

np.ndinfo(a)
# class: ndarray
# shape: (2, 3)
# strides: (3, 1)
# itemsize: 1
# data pointer: 0x...
# type: uint8

6.5.2. Gli strides spiegati

Uno stride indica quanti byte avanzare nel blocco di dati per spostarsi di un elemento lungo un dato asse. Per l’array uint8 2x3 sopra, gli strides sono (3, 1): spostarsi in basso di una riga salta 3 byte, spostarsi a destra di una colonna salta 1 byte. Equivale a dire che le righe sono memorizzate l’una di seguito all’altra, da sinistra a destra:

memory: [ 1 ][ 2 ][ 3 ][ 4 ][ 5 ][ 6 ]
          ^ row 0          ^ row 1
          <------- 3 bytes ---->

Per leggere a[i, j], numpy calcola i * strides[0] + j * strides[1] a partire dall’inizio del blocco di dati e legge itemsize byte da li. La stessa formula si estende a un numero qualsiasi di dimensioni.

Questa disposizione – righe memorizzate l’una dopo l’altra, con l’ultimo asse che varia piu rapidamente lungo la memoria – e chiamata ordine row-major. Ogni array che numpy alloca sulla camera usa questa disposizione.

6.5.3. Il row-major ha delle conseguenze

Dal fatto che le «righe sono memorizzate l’una di seguito all’altra» derivano due cose che contano quando si modella un buffer sulla camera.

L’ultimo asse e contiguo. Passare da a[0, 0] a a[0, 1] tocca il byte successivo. Passare da a[0, 0] a a[1, 0] salta attraverso un’intera riga.

L’ultimo asse e l’asse veloce per i calcoli sull’intero array. numpy sulla camera scorre sempre l’ultimo asse come piu interno, indipendentemente da quale asse risulti piu lungo. La libreria numpy desktop riordina silenziosamente i suoi cicli per mettere l’asse piu lungo come piu interno; la camera non lo fa, quindi una scelta di disposizione che il numpy desktop avrebbe mascherato costa comunque tempo qui. np.sum(m, axis=1) collassa l’ultimo asse e viene eseguito nella direzione contigua; np.sum(m, axis=0) no. Quando l’applicazione puo scegliere come disporre un buffer, metti l’asse lungo per ultimo cosi che le operazioni lungo di esso restino nel ciclo interno.

Se la disposizione parte sbagliata, transpose() (o la scorciatoia .T) la corregge senza copiare i dati – si limita a scambiare gli strides:

a = b.T            # now iterates fast

Prestazioni contiene la discussione completa sulle prestazioni.

6.5.4. Reshape, transpose, slicing – modifiche al descrittore

Qualsiasi operazione che riscrive solo il descrittore e gratuita. reshape scambia una nuova shape e nuovi strides sullo stesso blocco di dati. transpose inverte gli strides. a[::2] raddoppia uno stride. Ciascuna restituisce una vista dello stesso buffer sottostante.

Qualsiasi cosa che deve scorrere i dati e scrivere un nuovo buffer e una copia. La regola per ora e che le modifiche al descrittore sono gratuite e gli scorrimenti dei dati non lo sono.

6.5.5. Una nota su ndim

numpy sulla camera e compilato con un ndim massimo supportato pari a 4. Le operazioni che produrrebbero un array di rango superiore sollevano ValueError. La stragrande maggioranza del lavoro lato camera e 1-D o 2-D, quindi il limite raramente costituisce un problema.