6.5. Tvar a kroky

Data uvnitř ndarray tvoří jeden zabalený blok čísel. Deskriptor před tímto blokem rozhoduje o tom, jak se tento plochý blok čte jako tenzor.

6.5.1. Co deskriptor zaznamenává

Pět hodnot popisuje, jak číst datový blok jako tenzor:

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

Pomocná funkce ndinfo() vypíše všechny tyto hodnoty plus umístění podkladového bufferu v jediném volání. Dvě pole, jejichž umístění bufferů se shodují, sdílejí paměť:

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

6.5.2. Vysvětlení kroků (strides)

Krok (stride) je počet bajtů, o které je třeba postoupit v datovém bloku, abychom se posunuli o jeden prvek podél dané osy. U výše uvedeného pole 2x3 typu uint8 jsou kroky (3, 1): posun dolů o jeden řádek přeskočí 3 bajty, posun doprava o jeden sloupec přeskočí 1 bajt. To je totéž jako říci, že řádky jsou uloženy za sebou, zleva doprava:

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

Pro přečtení a[i, j] numpy vypočítá i * strides[0] + j * strides[1] od začátku datového bloku a odtud přečte itemsize bajtů. Stejný vzorec se rozšiřuje na libovolný počet dimenzí.

Toto rozložení – řádky uložené za sebou, kde se poslední osa v paměti mění nejrychleji – se nazývá pořadí row-major. Každé pole, které numpy na kameře alokuje, používá toto rozložení.

6.5.3. Row-major má důsledky

Z principu „řádky uložené za sebou“ vyplývají dvě věci, na kterých záleží při utváření tvaru bufferu na kameře.

Poslední osa je souvislá. Procházení z a[0, 0] na a[0, 1] se dotkne následujícího bajtu. Procházení z a[0, 0] na a[1, 0] přeskočí přes celý řádek.

Poslední osa je rychlá osa pro výpočty nad celým polem. numpy na kameře vždy prochází poslední osu nejvnitřněji, bez ohledu na to, která osa je náhodou delší. Knihovna numpy na desktopu tiše přeuspořádá své smyčky tak, aby byla nejdelší osa nejvnitřnější; kamera to nedělá, takže volba rozložení, kterou by desktopový numpy zakryl, zde stále stojí čas. np.sum(m, axis=1) sbaluje poslední osu a běží v souvislém směru; np.sum(m, axis=0) nikoli. Pokud má aplikace na výběr, jak rozložit buffer, umístěte dlouhou osu na konec, aby operace podél ní zůstaly ve vnitřní smyčce.

Pokud rozložení od začátku není správné, transpose() (nebo zkratka .T) jej opraví bez kopírování dat – pouze prohodí kroky:

a = b.T            # now iterates fast

Výkon obsahuje úplnou diskuzi o výkonu.

6.5.4. Reshape, transpose, slicing – úpravy deskriptoru

Jakákoli operace, která pouze přepisuje deskriptor, je zdarma. reshape prohodí nový shape a strides nad stejným datovým blokem. transpose obrátí kroky. a[::2] zdvojnásobí krok. Každá vrací pohled na stejný podkladový buffer.

Cokoli, co musí projít data a zapsat nový buffer, je kopie. Pravidlo pro tuto chvíli je, že úpravy deskriptoru jsou zdarma a procházení dat nikoli.

6.5.5. Poznámka o ndim

numpy na kameře je sestaven s maximálním podporovaným ndim rovným 4. Operace, které by vytvořily pole vyšší úrovně, vyvolají výjimku ValueError. Drtivá většina práce na straně kamery je 1-D nebo 2-D, takže tento limit je zřídka problémem.