NumPy配列のアドレス情報を調べる方法

Numpyをもっと深く知りたい方へ。かなりマニアックです。

NumPyのメモリ管理方法

NumPy配列とメタデータは、特定のデータが格納されるデータバッファという専用のメモリ領域に置かれており、一旦NumPy配列を初期化したら、メタデータとデータは、ランダムアクセスメモリ(RAM:Random Access Memory)内に割り当てられた位置に格納されます。

ここで気になるのは、ndarray型のデータがどのようにメモリにレイアウトされているかです。

これを知ることはとても重要です。

なぜなら、データが連続的にメモリに格納されていれば読み出しが早く、逆に途切れ途切れに格納されていれば読み出しが遅くなるからです。

扱うデータ量が多くなれば、計算速度にかなり影響が出ます。

特にリアルタイム処理を考えているなら知らないと致命傷になります。

NumPy配列のアドレス情報を調べるには

__array_interface__
これを配列の後ろに付けるとNumPy配列のアドレス情報が確認できます。

以下のリストを見てください。

import numpy as np

array_1 = np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12])
array_2 = array_1[::2]
array_3 = array_1[::4]

print('array_1 =', array_1)
print('array_2 =', array_2)
print('array_3 =', array_3)

print(array_1.__array_interface__)
print(array_2.__array_interface__)
print(array_3.__array_interface__)

これを実行すると以下となります。

array_1 = [ 0  1  2  3  4  5  6  7  8  9 10 11 12]
array_2 = [ 0  2  4  6  8 10 12]
array_3 = [ 0  4  8 12]

{'data': (1211476552720, False), 
'strides': None, 
'descr': [('', '<i4')], 
'typestr': '<i4', 
'shape': (13,), 
'version': 3}

{'data': (1211476552720, False), 
'strides': (8,), 
'descr': [('', '<i4')], 
'typestr': '<i4', 
'shape': (7,), 
'version': 3}

{'data': (1211476552720, False), 
'strides': (16,), 
'descr': [('', '<i4')], 
'typestr': '<i4', 
'shape': (4,), 
'version': 3}

出力された文字の意味は以下。

dataデータの格納場所です。
タプルの最初の要素はNumPy配列のメモリブロックアドレスです。
二番目の要素は読み出し専用か否か。False は読み出し専用ではないということです。
stridesその配列がC言語式の連続したメモリバッファかどうかを示しています。
Noneは連続したメモリバッファであることを示しています。
連続していないと計算時間が遅くなります。
Noneでない場合は、次の配列要素を取得するにはどこへ飛べばよいかの情報を示しています。
descrデフォルトは[(' ', typestr)]。
メモリレイアウトに関してより詳しい情報を提供できる場合があります。
typestr先頭から順に、バイトオーダー、文字コード、バイト数の3つの値です。
この場合、バイトオーダーはリトルエンディアン、文字コードは整数、バイト数は4を示しています。
shape次元のサイズ。array_1の場合は、1次元で要素数13を示しています。
versionバージョン番号です。この例では3です。

ここで特に重要な情報は strides です。

データはなるべく連続してメモリに格納されることが重要です。

array_2 や array_3 のように元の array_1 からスライスして別な配列を作成すると strides が(8,)や(16,)になります。

この例は1次元なのでこの程度の strides ですが、3次元配列ならさらに大きくなります。

つまり、メモリからCPUにデータを読み出す際、strides の分だけ異なるメモリ位置に飛ばされます。

その分、計算時間が遅くなるということです。

CPUのキャッシュを使用して性能を高めるためには、strides は None またはなるべく小さい方が良いということです。

まとめ

  • NumPy配列のアドレス情報を調べるには、「__array_interface__」をNumPy配列(ndarray型の配列)の後ろに付ける。
  • 特に重要な情報は strides が None であるか否か。
  • strides が None ならデータはメモリに連続格納されている。
  • 効率的なコードを書きたいならNumPy配列を気にした方がよい。