Cython で多くの 3D メモリビューを使用しています。
cython.declare(a='double[:, :, ::1]')
a = np.empty((10, 20, 30), dtype='double')
のすべての要素をループしたいことがよくありa
ます。次のようなトリプルループを使用してこれを行うことができます
for i in range(a.shape[0]):
for j in range(a.shape[1]):
for k in range(a.shape[2]):
a[i, j, k] = ...
i
インデックス、j
および を気にしない場合は、次k
のようにフラット ループを実行する方が効率的です。
cython.declare(a_ptr='double*')
a_ptr = cython.address(a[0, 0, 0])
for i in range(size):
a_ptr[i] = ...
size
ここで、配列内の要素数 ( ) を知る必要があります。shape
これは、属性の要素の積、つまりsize = a.shape[0]*a.shape[1]*a.shape[2]
、またはより一般的には によって与えられますsize = np.prod(np.asarray(a).shape)
。これらはどちらも書きにくいと思いますし、(小さいながらも) 計算上のオーバーヘッドが気になります。これを行う良い方法はsize
、memoryviews の組み込み属性を使用することですsize = a.size
。ただし、理解できない理由により、Cython によって生成された注釈 html ファイルから明らかなように、これは最適化されていない C コードにつながります。具体的には、によって生成される C コードsize = a.shape[0]*a.shape[1]*a.shape[2]
は単純です。
__pyx_v_size = (((__pyx_v_a.shape[0]) * (__pyx_v_a.shape[1])) * (__pyx_v_a.shape[2]));
から生成されたCコードはどこにありますsize = a.size
か
__pyx_t_10 = __pyx_memoryview_fromslice(__pyx_v_a, 3, (PyObject *(*)(char *)) __pyx_memview_get_double, (int (*)(char *, PyObject *)) __pyx_memview_set_double, 0);; if (unlikely(!__pyx_t_10)) __PYX_ERR(0, 2238, __pyx_L1_error)
__Pyx_GOTREF(__pyx_t_10);
__pyx_t_14 = __Pyx_PyObject_GetAttrStr(__pyx_t_10, __pyx_n_s_size); if (unlikely(!__pyx_t_14)) __PYX_ERR(0, 2238, __pyx_L1_error)
__Pyx_GOTREF(__pyx_t_14);
__Pyx_DECREF(__pyx_t_10); __pyx_t_10 = 0;
__pyx_t_7 = __Pyx_PyIndex_AsSsize_t(__pyx_t_14); if (unlikely((__pyx_t_7 == (Py_ssize_t)-1) && PyErr_Occurred())) __PYX_ERR(0, 2238, __pyx_L1_error)
__Pyx_DECREF(__pyx_t_14); __pyx_t_14 = 0;
__pyx_v_size = __pyx_t_7;
上記のコードを生成するために、コンパイラ ディレクティブを使用して可能なすべての最適化を有効にしました。つまり、 によって生成された扱いにくい C コードをa.size
最適化して取り除くことはできません。size
「属性」は実際には事前に計算された属性ではなく、実際にはルックアップ時に計算を実行するように見えます。shape
さらに、この計算は、単純に属性の積を取得するよりもかなり複雑です。docsに説明のヒントが見つかりません。
a.shape[0]*a.shape[1]*a.shape[2]
この動作の説明は何ですか?このマイクロ最適化を本当に気にかけているのであれば、書くよりも良い選択がありますか?