Ciągła tablica to po prostu tablica przechowywana w nieprzerwanym bloku pamięci: aby uzyskać dostęp do następnej wartości w tablicy, po prostu przechodzimy do następnego adresu pamięci.
Rozważ tablicę 2D arr = np.arange(12).reshape(3,4)
. To wygląda tak:
W pamięci komputera wartości arr
są przechowywane w następujący sposób:
Oznacza to, że arr
jest to ciągła tablica C, ponieważ wiersze są przechowywane jako ciągłe bloki pamięci. Następny adres pamięci przechowuje wartość następnego wiersza w tym wierszu. Jeśli chcemy przeskoczyć kolumnę w dół, wystarczy przeskoczyć przez trzy bloki (np. Przeskoczenie z 0 na 4 oznacza, że przeskakujemy o 1, 2 i 3).
Transpozycja tablicy za arr.T
pomocą oznacza, że ciągłość C jest tracona, ponieważ sąsiednie wpisy wierszy nie znajdują się już w sąsiednich adresach pamięci. Jednak arr.T
czy Fortran jest ciągły, ponieważ kolumny są w ciągłych blokach pamięci:
Pod względem wydajności dostęp do adresów pamięci, które są obok siebie, jest bardzo często szybszy niż uzyskiwanie dostępu do adresów, które są bardziej „rozłożone” (pobieranie wartości z pamięci RAM może wiązać się z pobieraniem wielu sąsiednich adresów i zapisywaniem ich w pamięci podręcznej dla procesora). oznacza, że operacje na sąsiadujących tablicach często będą szybsze.
W konsekwencji ciągłego układu pamięci w języku C operacje na wierszach są zwykle szybsze niż operacje na kolumnach. Na przykład zazwyczaj to znajdziesz
np.sum(arr, axis=1)
jest nieco szybszy niż:
np.sum(arr, axis=0)
Podobnie operacje na kolumnach będą nieco szybsze w przypadku ciągłych tablic w języku Fortran.
Wreszcie, dlaczego nie możemy spłaszczyć ciągłej tablicy Fortran przez przypisanie nowego kształtu?
>>> arr2 = arr.T
>>> arr2.shape = 12
AttributeError: incompatible shape for a non-contiguous array
Aby to było możliwe, NumPy musiałby złożyć takie rzędy w arr.T
następujący sposób:
(Ustawienie shape
atrybutu bezpośrednio zakłada kolejność C - tj. NumPy próbuje wykonać operację w wierszach).
To niemożliwe. Dla każdej osi NumPy musi mieć stałą długość kroku (liczbę bajtów do przesunięcia), aby dostać się do następnego elementu tablicy. Spłaszczeniearr.T
w ten sposób wymagałoby przeskakiwania do przodu i do tyłu w pamięci w celu pobrania kolejnych wartości tablicy.
Gdybyśmy arr2.reshape(12)
zamiast tego napisali , NumPy skopiowałby wartości arr2 do nowego bloku pamięci (ponieważ nie może zwrócić widoku oryginalnych danych dla tego kształtu).
arr2
na 1D(12,)
używa kolejności C, co oznacza, że ta oś 1 jest rozwijana przed osią 0 (tj. Każdy z czterech rzędów musi być umieszczony obok siebie, aby utworzyć pożądaną tablicę 1D). Niemożliwe jest odczytanie tej sekwencji liczb całkowitych (0, 4, 8, 1, 5, 9, 2, 6, 10, 3, 7, 11) z bufora przy użyciu stałej długości kroku (bajty do przejścia do odwiedzenia te elementy w kolejności to 4, 4, -7, 4, 4, -7, 4, 4, 7, 4, 4). NumPy wymaga stałej długości kroku na oś.arr[:, ::-1]
jest to widok tego samego bufora pamięci, coarr
NumPy nie bierze go pod uwagę w kolejności C lub F, ponieważ przechodził przez wartości w buforze w "niestandardowej" kolejności ...Może ten przykład z 12 różnymi wartościami tablicowymi pomoże:
In [207]: x=np.arange(12).reshape(3,4).copy() In [208]: x.flags Out[208]: C_CONTIGUOUS : True F_CONTIGUOUS : False OWNDATA : True ... In [209]: x.T.flags Out[209]: C_CONTIGUOUS : False F_CONTIGUOUS : True OWNDATA : False ...
Te
C order
wartości są w kolejności, w jakiej zostały one uzyskane w. Transponowanego te nie sąIn [212]: x.reshape(12,) # same as x.ravel() Out[212]: array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]) In [213]: x.T.reshape(12,) Out[213]: array([ 0, 4, 8, 1, 5, 9, 2, 6, 10, 3, 7, 11])
Możesz uzyskać 1d widoki obu
In [214]: x1=x.T In [217]: x.shape=(12,)
kształt
x
można również zmienić.In [220]: x1.shape=(12,) --------------------------------------------------------------------------- AttributeError Traceback (most recent call last) <ipython-input-220-cf2b1a308253> in <module>() ----> 1 x1.shape=(12,) AttributeError: incompatible shape for a non-contiguous array
Ale kształtu transpozycji nie można zmienić.
data
Jest jeszcze w0,1,2,3,4...
porządku, który nie może być dostępna jako dostęp0,4,8...
w 1d tablicy.Ale kopię
x1
można zmienić:In [227]: x2=x1.copy() In [228]: x2.flags Out[228]: C_CONTIGUOUS : True F_CONTIGUOUS : False OWNDATA : True ... In [229]: x2.shape=(12,)
Patrzenie
strides
może też pomóc. Strides to odległość (w bajtach), jaką musi przejść, aby przejść do następnej wartości. Dla tablicy 2d będą dwie wartości krokowe:In [233]: x=np.arange(12).reshape(3,4).copy() In [234]: x.strides Out[234]: (16, 4)
Aby przejść do następnego wiersza, krok 16 bajtów, następna kolumna tylko 4.
In [235]: x1.strides Out[235]: (4, 16)
Transpozycja zmienia tylko kolejność kroków. Następny wiersz to tylko 4 bajty - czyli następna liczba.
In [236]: x.shape=(12,) In [237]: x.strides Out[237]: (4,)
Zmiana kształtu zmienia również kroki - po prostu przechodź przez bufor po 4 bajty na raz.
In [238]: x2=x1.copy() In [239]: x2.strides Out[239]: (12, 4)
Mimo że
x2
wygląda tak samox1
, ma swój własny bufor danych, z wartościami w innej kolejności. Następna kolumna ma teraz 4 bajty więcej, a następny wiersz to 12 (3 * 4).In [240]: x2.shape=(12,) In [241]: x2.strides Out[241]: (4,)
I tak jak w przypadku
x
zmiany kształtu na 1d zmniejsza liczbę kroków do(4,)
.Ponieważ
x1
przy danych w0,1,2,...
kolejności nie ma kroku 1d, który by dał0,4,8...
.__array_interface__
to kolejny przydatny sposób wyświetlania informacji o tablicy:In [242]: x1.__array_interface__ Out[242]: {'strides': (4, 16), 'typestr': '<i4', 'shape': (4, 3), 'version': 3, 'data': (163336056, False), 'descr': [('', '<i4')]}
x1
Adres bufora danych będzie taka sama jak w przypadkux
, z którym dzieli dane.x2
ma inny adres bufora.Możesz także poeksperymentować z dodaniem
order='F'
parametru do poleceńcopy
ireshape
.źródło