6.5. Vorm en strides¶
De data binnen een ndarray is één gecomprimeerd blok getallen. De descriptor vóór dat blok bepaalt hoe dat platte blok wordt uitgelezen als een tensor.
6.5.1. Wat de descriptor vastlegt¶
Vijf waarden beschrijven hoe het datablok als een tensor gelezen moet worden:
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
De helper ndinfo() print ze allemaal plus de locatie van de onderliggende buffer in één aanroep. Twee arrays waarvan de bufferlocaties overeenkomen delen geheugen:
np.ndinfo(a)
# class: ndarray
# shape: (2, 3)
# strides: (3, 1)
# itemsize: 1
# data pointer: 0x...
# type: uint8
6.5.2. Strides uitgelegd¶
Een stride is hoeveel bytes je in het datablok moet stappen om één element langs een bepaalde as te verplaatsen. Voor de 2x3 uint8-array hierboven zijn de strides (3, 1): één rij naar beneden gaan springt 3 bytes, één kolom naar rechts gaan springt 1 byte. Dat is hetzelfde als zeggen dat de rijen achter elkaar zijn opgeslagen, van links naar rechts:
memory: [ 1 ][ 2 ][ 3 ][ 4 ][ 5 ][ 6 ]
^ row 0 ^ row 1
<------- 3 bytes ---->
Om a[i, j] te lezen berekent numpy i * strides[0] + j * strides[1] vanaf het begin van het datablok en leest itemsize bytes vanaf daar. Dezelfde formule breidt zich uit naar een willekeurig aantal dimensies.
Deze indeling – rijen achter elkaar opgeslagen, met de laatste as die het snelst varieert door het geheugen – wordt row-major-volgorde genoemd. Elke array die numpy op de camera alloceert gebruikt deze indeling.
6.5.3. Row-major heeft gevolgen¶
Twee dingen vloeien voort uit “rijen achter elkaar opgeslagen” die er toe doen bij het vormgeven van een buffer op de camera.
De laatste as is aaneengesloten. Van a[0, 0] naar a[0, 1] lopen raakt de eerstvolgende byte. Van a[0, 0] naar a[1, 0] lopen springt over een hele rij.
De laatste as is de snelle as voor rekenwerk op hele arrays. numpy op de camera doorloopt altijd de laatste as als binnenste, ongeacht welke as toevallig langer is. De desktop-numpy-bibliotheek herordent stilzwijgend haar lussen om de langste as het binnenst te plaatsen; de camera doet dat niet, dus een indelingskeuze die desktop-numpy zou hebben verdoezeld kost hier nog steeds tijd. np.sum(m, axis=1) reduceert de laatste as en loopt in de aaneengesloten richting; np.sum(m, axis=0) niet. Wanneer de applicatie een keuze heeft over hoe een buffer wordt ingedeeld, plaats dan de lange as als laatste zodat bewerkingen erlangs in de binnenste lus blijven.
Als de indeling verkeerd begint, herstelt transpose() (of de snelkoppeling .T) dit zonder de data te kopiëren – het wisselt simpelweg de strides om:
a = b.T # now iterates fast
Prestaties bevat de volledige prestatiebespreking.
6.5.4. Reshape, transpose, slicing – descriptorbewerkingen¶
Elke bewerking die alleen de descriptor herschrijft is gratis. reshape wisselt een nieuwe shape en strides om over hetzelfde datablok. transpose keert de strides om. a[::2] verdubbelt een stride. Elk retourneert een view van dezelfde onderliggende buffer.
Alles wat de data moet doorlopen en een nieuwe buffer moet schrijven is een kopie. De regel voor nu is dat descriptorbewerkingen gratis zijn en datadoorlopen niet.
6.5.5. Een opmerking over ndim¶
numpy op de camera is gebouwd met een maximaal ondersteunde ndim van 4. Bewerkingen die een array van hogere rang zouden produceren veroorzaken een ValueError. Het overgrote deel van het werk aan de camerakant is 1-D of 2-D, dus de limiet is zelden een probleem.