6.5. Formato e strides¶
Os dados dentro de um ndarray são um único bloco compactado de números. O descritor à frente desse bloco decide como esse bloco plano é lido como um tensor.
6.5.1. O que o descritor registra¶
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, além da localização do buffer subjacente, em uma única chamada. Dois arrays cujas localizações de buffer coincidem estão compartilhando 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 pular no bloco de dados para avançar um elemento ao longo de um dado eixo. Para o array uint8 2x3 acima, os strides são (3, 1): descer uma linha pula 3 bytes, ir uma coluna para a direita pula 1 byte. Isso é o mesmo que dizer que as linhas são armazenadas uma após a outra, 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 dali. A mesma fórmula se estende a qualquer número de dimensões.
Esse layout – linhas armazenadas uma após a outra, com o último eixo variando mais rápido na memória – é chamado de ordem row-major. Todo array que o numpy aloca na câmera usa esse layout.
6.5.3. Row-major tem consequências¶
Duas coisas decorrem de “linhas armazenadas uma após a outra” que importam ao moldar um buffer na câmera.
O último eixo é contíguo. Caminhar de a[0, 0] para a[0, 1] toca o byte seguinte. Caminhar de a[0, 0] para a[1, 0] salta uma linha inteira.
O último eixo é o eixo rápido para a matemática sobre o array inteiro. O numpy na câmera sempre percorre o último eixo no laço mais interno, independentemente de qual eixo seja mais longo. A biblioteca numpy de desktop reordena silenciosamente seus laços para colocar o eixo mais longo no laço mais interno; a câmera não faz isso, então uma escolha de layout que o numpy de desktop teria disfarçado ainda custa tempo aqui. np.sum(m, axis=1) colapsa o último eixo e roda 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 operações ao longo dele permaneçam no laço interno.
Se o layout começa errado, transpose() (ou o atalho .T) o corrige sem copiar os dados – ele apenas troca os strides:
a = b.T # now iterates fast
Desempenho traz a discussão completa de desempenho.
6.5.4. Reshape, transpose, fatiamento – edições do descritor¶
Qualquer operação que apenas reescreve o descritor é gratuita. reshape troca um novo shape e strides sobre o mesmo bloco de dados. transpose inverte os strides. a[::2] dobra um stride. Cada uma retorna um view do mesmo buffer subjacente.
Qualquer coisa que precise percorrer os dados e escrever um novo buffer é uma cópia. A regra por ora é que edições do descritor são gratuitas e percursos de dados não são.
6.5.5. Uma nota sobre ndim¶
O numpy na câmera é construído com um ndim máximo suportado de 4. Operações que produziriam um array de posto mais alto geram ValueError. A grande maioria do trabalho do lado da câmera é 1-D ou 2-D, então o limite raramente é um problema.