6.5. 形状とストライド¶
ndarray の内部のデータは、パックされた数値の 1 つのブロックです。そのブロックの前にあるディスクリプタが、そのフラットなブロックをテンソルとしてどう読み出すかを決めます。
6.5.1. ディスクリプタが記録するもの¶
5 つの値が、データブロックをテンソルとしてどう読むかを記述します:
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
ndinfo() ヘルパーは、それらすべてに加えて基盤バッファの位置を 1 回の呼び出しで表示します。バッファ位置が一致する 2 つの配列はメモリを共有しています:
np.ndinfo(a)
# class: ndarray
# shape: (2, 3)
# strides: (3, 1)
# itemsize: 1
# data pointer: 0x...
# type: uint8
6.5.2. ストライドの説明¶
ストライド とは、ある軸に沿って 1 要素移動するためにデータブロック内で進むバイト数です。上の 2x3 の uint8 配列では、ストライドは (3, 1) です。1 行下に移動すると 3 バイト進み、1 列右に移動すると 1 バイト進みます。これは、行が左から右へ連続して格納されていると言うのと同じことです:
memory: [ 1 ][ 2 ][ 3 ][ 4 ][ 5 ][ 6 ]
^ row 0 ^ row 1
<------- 3 bytes ---->
a[i, j] を読むために、numpy はデータブロックの先頭から i * strides[0] + j * strides[1] を計算し、そこから itemsize バイトを読み取ります。同じ式が任意の次元数に拡張されます。
このレイアウト(行が端から端まで格納され、最終軸がメモリに沿って最も速く変化する)は 行優先(row-major) 順序と呼ばれます。numpy がカメラ上でアロケートするすべての配列はこのレイアウトを使用します。
6.5.3. 行優先には結果が伴う¶
「行が連続して格納される」ことから、カメラ上でバッファを形作る際に重要となる 2 つのことが導かれます。
最終軸は連続している。 a[0, 0] から a[0, 1] へ進むと、すぐ隣のバイトに触れます。a[0, 0] から a[1, 0] へ進むと、1 行分を飛び越えます。
最終軸は配列全体の演算における高速軸である。 カメラ上の numpy は、どの軸がたまたま長いかにかかわらず、常に最終軸を最内ループでたどります。デスクトップの numpy ライブラリは最も長い軸を最内に置くようにループを暗黙的に並べ替えますが、カメラはそうしません。そのため、デスクトップの numpy なら覆い隠されていたであろうレイアウトの選択が、ここでは依然として時間のコストになります。np.sum(m, axis=1) は最終軸を畳み込み、連続方向で実行されます。np.sum(m, axis=0) はそうではありません。アプリケーションがバッファのレイアウトを選べる場合は、それに沿った操作が内側のループにとどまるよう、長い軸を最後に置いてください。
レイアウトが最初から間違っている場合、transpose()(または .T ショートカット)がデータをコピーせずにそれを修正します。ストライドを入れ替えるだけです:
a = b.T # now iterates fast
パフォーマンス にパフォーマンスに関する完全な議論があります。
6.5.4. reshape、transpose、スライス -- ディスクリプタの編集¶
ディスクリプタを書き換えるだけの操作はコストがかかりません。reshape は同じデータブロックに対して新しい shape と strides を入れ替えます。transpose はストライドを反転します。a[::2] はストライドを 2 倍にします。それぞれが同じ基盤バッファの ビュー を返します。
データをたどって新しいバッファを書き込む必要のあるものはコピーです。今のところの規則は、ディスクリプタの編集はコストがかからず、データのたどりはコストがかかるということです。
6.5.5. ndim についての注記¶
カメラ上の numpy は、サポートされる ndim の最大値が 4 でビルドされています。より高ランクの配列を生成する操作は ValueError を送出します。カメラ側の作業の大半は 1 次元または 2 次元なので、この制限が問題になることはめったにありません。