rsp

Cython で連続メモリ領域としてアドレスを取れる配列たち

Cython で C API との連携をする際、 連続メモリ領域を確保してアドレスを渡すような場面によく遭遇します。

libc.malloc を使わずに*1これを実現するためには、 cdef 宣言できるような型が用意されている 必要があり、それを叶える方法がいくつかあります。

コンパイル時にサイズを決定するもの:

  • C 言語風の配列

実行時にサイズを決定できるもの:

  • cpython.array
  • cython.view.array
  • 型付きメモリビューと numpy.array

C 言語風の配列

サイズは コンパイル に決定する必要があります。

cdef char c_buffer[10]
# アドレスを取得
print(<size_t> c_buffer)
# => 140734724932822

# 要素数に変数は使えない
# cdef char c_buffer[dim]
# => Not allowed in a constant expression

cpython.array

Cython には、Python の処理系のうち C 言語で実装されている CPython のモジュールへのインターフェイスが用意されています。

github.com/cython/Cython/Includes/cpython

このなかで、 arrayモジュールへのインターフェイス を使うことで、連続メモリ領域を cdef 宣言できます。

実行時にサイズ(dim)を決定できます。

from cpython cimport array as cpython_array

dim = 10
cdef cpython_array.array cpython_buffer
    = cpython_array.array('b', range(dim))
# アドレスを取得
print(<size_t> cpython_buffer.data.as_voidptr)
# => 4448305408

初期化の箇所の 'b' については、 Python の array の型コード に準じます。

アドレス取得の箇所については、 data が 各型用ポインタの union になっている ので、ここでは void* として取り出しています。

cython.view.array

Cython の array を使います。

実行時にサイズ(dim)を決定できます。

from cython.view cimport array as cython_array

dim = 10
cdef cython_array cython_buffer
     = cython_array(shape=(dim,), itemsize=sizeof(char), format='b')
# アドレスを取得
print(<size_t> cython_buffer.data)
# => 140489752843568

# [ ] の結果は Python オブジェクトのため不可
# print(<size_t>&cython_buffer[0])
# => Cannot take address of Python object

datachar* ポインタなので、そこからアドレスを取れます。

次の項で紹介する「型付きメモリビュー」をとることでも、アドレスを取得できます。

cdef char[:] view = cython_buffer   # Typed Memoryview
print(<size_t>&view[0])

型付きメモリビューと numpy.array

Numpy で領域を確保し、 Cython の 型付きメモリビュー で参照します。もちろんサイズは実行時に与えることができます。

import numpy

dim = 10
np_buffer = numpy.arange(dim, dtype=numpy.dtype('b'))
cdef char[:] view = np_buffer
# アドレスを取得
print(<size_t>&view[0])
# => 140502025142752

# 直接アドレス取得はできない
# print(<size_t>&np_buffer[0])
# => Cannot take address of Python object

参考 URL

*1:自分でメモリの寿命を管理したくないですよね。