6.5. Forma e strides¶
Os dados dentro de um ndarray são um bloco compacto de números. O descritor na frente desse bloco decide como esse bloco plano é lido como um tensor.
6.5.1. O que o descritor regista¶
Cinco valores descrevem como ler o bloco de dados como um 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
O auxiliar ndinfo() imprime todos eles mais a localização do buffer subjacente numa única chamada. Dois arrays cujas localizações de buffer coincidem estão a partilhar memória:
np.ndinfo(a)
# class: ndarray
# shape: (2, 3)
# strides: (3, 1)
# itemsize: 1
# data pointer: 0x...
# type: uint8
6.5.2. Strides explicados¶
Um stride é quantos bytes avançar no bloco de dados para se mover um elemento ao longo de um dado eixo. Para o array uint8 2x3 acima, os strides são (3, 1): mover uma linha para baixo salta 3 bytes, mover uma coluna para a direita salta 1 byte. Isto é o mesmo que dizer que as linhas são armazenadas consecutivamente, da esquerda para a direita:
memory: [ 1 ][ 2 ][ 3 ][ 4 ][ 5 ][ 6 ]
^ row 0 ^ row 1
<------- 3 bytes ---->
Para ler a[i, j], o numpy calcula i * strides[0] + j * strides[1] a partir do início do bloco de dados e lê itemsize bytes a partir daí. A mesma fórmula estende-se a qualquer número de dimensões.
Esta disposição – linhas armazenadas de seguida, com o último eixo a variar mais rapidamente ao longo da memória – chama-se ordem row-major. Todos os arrays que o numpy aloca na câmara usam esta disposição.
6.5.3. Row-major tem consequências¶
Duas coisas decorrem de «linhas armazenadas consecutivamente» que importam ao dar forma a um buffer na câmara.
O último eixo é contíguo. Percorrer a[0, 0] a a[0, 1] toca no byte seguinte. Percorrer a[0, 0] a a[1, 0] salta uma linha inteira.
O último eixo é o eixo rápido para matemática em arrays inteiros. O numpy na câmara percorre sempre o último eixo mais internamente, independentemente do eixo que for mais longo. A biblioteca numpy de desktop reordena silenciosamente os seus ciclos para colocar o eixo mais longo mais internamente; a câmara não o faz, por isso uma escolha de disposição que o numpy de desktop teria ignorado ainda custa tempo aqui. np.sum(m, axis=1) colapsa o último eixo e corre na direção contígua; np.sum(m, axis=0) não. Quando a aplicação tem escolha sobre como dispor um buffer, coloque o eixo longo por último para que as operações ao longo dele fiquem no ciclo interno.
Se a disposição estiver errada, transpose() (ou o atalho .T) corrige-a sem copiar os dados – apenas troca os strides:
a = b.T # now iterates fast
Desempenho tem a discussão completa sobre desempenho.
6.5.4. Reshape, transpose, fatiamento – edições ao descritor¶
Qualquer operação que apenas reescreva o descritor é gratuita. reshape troca uma nova shape e strides no mesmo bloco de dados. transpose inverte os strides. a[::2] duplica um stride. Cada uma devolve uma vista do mesmo buffer subjacente.
Qualquer coisa que tenha de percorrer os dados e escrever um novo buffer é uma cópia. A regra por agora é que as edições ao descritor são gratuitas e as travessias de dados não.
6.5.5. Uma nota sobre ndim¶
O numpy na câmara é construído com um máximo suportado de ndim igual a 4. Operações que produziriam um array de rank superior lançam ValueError. A grande maioria do trabalho do lado da câmara é 1-D ou 2-D, por isso o limite raramente é um problema.