Cython:了解具有 indirect_contignuous 内存布局的类型化内存视图
Cython: understanding a typed memoryview with a indirect_contignuous memory layout
想进一步了解Cython的厉害typed-memoryviews和内存布局indirect_contiguous
。
根据documentation indirect_contiguous
时使用 "the list of pointers is contiguous".
还有一个用法示例:
# contiguous list of pointers to contiguous lists of ints
cdef int[::view.indirect_contiguous, ::1] b
所以如果我错了请纠正我,但我假设 "contiguous list of pointers to contiguous lists of ints" 表示类似于由以下 c++ 虚拟代码创建的数组:
// we want to create a 'contiguous list of pointers to contiguous lists of ints'
int** array;
// allocate row-pointers
// This is the 'contiguous list of pointers' related to the first dimension:
array = new int*[ROW_COUNT]
// allocate some rows, each row is a 'contiguous list of ints'
array[0] = new int[COL_COUNT]{1,2,3}
因此,如果我理解正确,那么在我的 Cython 代码中应该可以从 int**
中获取内存视图,如下所示:
cdef int** list_of_pointers = get_pointers()
cdef int[::view.indirect_contiguous, ::1] view = <int[:ROW_COUNT:view.indirect_contiguous,COL_COUNT:1]> list_of_pointers
但是我得到了编译错误:
cdef int[::view.indirect_contiguous, ::1] view = <int[:ROW_COUNT:view.indirect_contiguous,:COL_COUNT:1]> list_of_pointers
^
------------------------------------------------------------
memview_test.pyx:76:116: Pointer base type does not match cython.array base type
我做错了什么?
我是否遗漏了任何转换或者我是否误解了 indirect_contiguous 的概念?
让我们澄清一下:类型化内存视图只能用于实现 buffer-protocol.
的对象
原始 C 指针显然没有实现缓冲区协议。但是您可能会问,为什么像下面的快速和肮脏的代码这样的东西可以工作:
%%cython
from libc.stdlib cimport calloc
def f():
cdef int* v=<int *>calloc(4, sizeof(int))
cdef int[:] b = <int[:4]>v
return b[0] # leaks memory, so what?
这里,一个指针(v
)被用来构造一个类型化的内存视图(b
)。然而,在幕后还有更多内容(如 cythonized c 文件中所示):
- 构造了cython-array(即
cython.view.array
),它包装了原始指针并可以通过buffer-protocol 公开它
- 此数组用于创建类型化内存视图。
您理解 view.indirect_contiguous
的用途是正确的 - 这正是您想要的。然而,问题是view.array
,它无法处理这种类型的数据布局。
view.indirect
和 view.indirect_contiguous
对应 PyBUF_INDIRECT
in protocol-buffer parlance and for this the field suboffsets
must contain some meaningful values (i.e >=0
for some dimensions). However, as can be see in the source-code view.array
根本没有这个成员——根本没有办法表示复杂的内存布局!
它把我们留在哪里?正如@chrisb 和@DavidW 在您的其他问题中指出的那样,您将必须实现一个包装器,它可以通过协议缓冲区公开您的数据结构。
Python 中有数据结构使用间接内存布局 - 最突出的是 PIL 数组。一个很好的理解起点,suboffsets
应该如何工作是 this piece of documenation:
void *get_item_pointer(int ndim, void *buf, Py_ssize_t *strides,
Py_ssize_t *suboffsets, Py_ssize_t *indices) {
char *pointer = (char*)buf; // A
int i;
for (i = 0; i < ndim; i++) {
pointer += strides[i] * indices[i]; // B
if (suboffsets[i] >=0 ) {
pointer = *((char**)pointer) + suboffsets[i]; // C
}
}
return (void*)pointer; // D
}
在你的情况下 strides
和 offsets
将是
strides=[sizeof(int*), sizeof(int)]
(即在通常的 x86_64
机器上 [8,4]
)
offsets=[0,-1]
,即只有第一个维度是间接的。
获取元素 [x,y]
的地址将发生如下情况:
- 行
A
中,pointer
设置为buf
,我们假设BUF
.
- 第一维度:
- 在行
B
中,pointer
变为BUF+x*8
,指向第x行指针所在的位置。
- 因为
suboffsets[0]>=0
,我们取消引用行 C
中的指针,因此它显示地址 ROW_X
- 第 x 行的开始。
- 第二维度:
- 在行
B
中,我们使用 strides
获取 y
元素的地址,即 pointer=ROW_X+4*y
- 第二个维度是直接的(由
suboffset[1]<0
发出信号),因此不需要取消引用。
- 我们完成了,
pointer
指向所需的地址并在行 D
中返回。
FWIW,我实现了一个能够通过缓冲协议导出 int**
和类似内存布局的库:https://github.com/realead/indirect_buffer.
想进一步了解Cython的厉害typed-memoryviews和内存布局indirect_contiguous
。
根据documentation indirect_contiguous
时使用 "the list of pointers is contiguous".
还有一个用法示例:
# contiguous list of pointers to contiguous lists of ints
cdef int[::view.indirect_contiguous, ::1] b
所以如果我错了请纠正我,但我假设 "contiguous list of pointers to contiguous lists of ints" 表示类似于由以下 c++ 虚拟代码创建的数组:
// we want to create a 'contiguous list of pointers to contiguous lists of ints'
int** array;
// allocate row-pointers
// This is the 'contiguous list of pointers' related to the first dimension:
array = new int*[ROW_COUNT]
// allocate some rows, each row is a 'contiguous list of ints'
array[0] = new int[COL_COUNT]{1,2,3}
因此,如果我理解正确,那么在我的 Cython 代码中应该可以从 int**
中获取内存视图,如下所示:
cdef int** list_of_pointers = get_pointers()
cdef int[::view.indirect_contiguous, ::1] view = <int[:ROW_COUNT:view.indirect_contiguous,COL_COUNT:1]> list_of_pointers
但是我得到了编译错误:
cdef int[::view.indirect_contiguous, ::1] view = <int[:ROW_COUNT:view.indirect_contiguous,:COL_COUNT:1]> list_of_pointers
^
------------------------------------------------------------
memview_test.pyx:76:116: Pointer base type does not match cython.array base type
我做错了什么? 我是否遗漏了任何转换或者我是否误解了 indirect_contiguous 的概念?
让我们澄清一下:类型化内存视图只能用于实现 buffer-protocol.
的对象原始 C 指针显然没有实现缓冲区协议。但是您可能会问,为什么像下面的快速和肮脏的代码这样的东西可以工作:
%%cython
from libc.stdlib cimport calloc
def f():
cdef int* v=<int *>calloc(4, sizeof(int))
cdef int[:] b = <int[:4]>v
return b[0] # leaks memory, so what?
这里,一个指针(v
)被用来构造一个类型化的内存视图(b
)。然而,在幕后还有更多内容(如 cythonized c 文件中所示):
- 构造了cython-array(即
cython.view.array
),它包装了原始指针并可以通过buffer-protocol 公开它
- 此数组用于创建类型化内存视图。
您理解 view.indirect_contiguous
的用途是正确的 - 这正是您想要的。然而,问题是view.array
,它无法处理这种类型的数据布局。
view.indirect
和 view.indirect_contiguous
对应 PyBUF_INDIRECT
in protocol-buffer parlance and for this the field suboffsets
must contain some meaningful values (i.e >=0
for some dimensions). However, as can be see in the source-code view.array
根本没有这个成员——根本没有办法表示复杂的内存布局!
它把我们留在哪里?正如@chrisb 和@DavidW 在您的其他问题中指出的那样,您将必须实现一个包装器,它可以通过协议缓冲区公开您的数据结构。
Python 中有数据结构使用间接内存布局 - 最突出的是 PIL 数组。一个很好的理解起点,suboffsets
应该如何工作是 this piece of documenation:
void *get_item_pointer(int ndim, void *buf, Py_ssize_t *strides,
Py_ssize_t *suboffsets, Py_ssize_t *indices) {
char *pointer = (char*)buf; // A
int i;
for (i = 0; i < ndim; i++) {
pointer += strides[i] * indices[i]; // B
if (suboffsets[i] >=0 ) {
pointer = *((char**)pointer) + suboffsets[i]; // C
}
}
return (void*)pointer; // D
}
在你的情况下 strides
和 offsets
将是
strides=[sizeof(int*), sizeof(int)]
(即在通常的x86_64
机器上[8,4]
)offsets=[0,-1]
,即只有第一个维度是间接的。
获取元素 [x,y]
的地址将发生如下情况:
- 行
A
中,pointer
设置为buf
,我们假设BUF
. - 第一维度:
- 在行
B
中,pointer
变为BUF+x*8
,指向第x行指针所在的位置。 - 因为
suboffsets[0]>=0
,我们取消引用行C
中的指针,因此它显示地址ROW_X
- 第 x 行的开始。
- 在行
- 第二维度:
- 在行
B
中,我们使用strides
获取y
元素的地址,即pointer=ROW_X+4*y
- 第二个维度是直接的(由
suboffset[1]<0
发出信号),因此不需要取消引用。
- 在行
- 我们完成了,
pointer
指向所需的地址并在行D
中返回。
FWIW,我实现了一个能够通过缓冲协议导出 int**
和类似内存布局的库:https://github.com/realead/indirect_buffer.