Passing/Returning Cython Memoryviews 与 NumPy 数组
Passing/Returning Cython Memoryviews vs NumPy Arrays
我正在编写 Python 代码来加速二值图像中标记对象的区域属性函数。以下代码将在给定对象索引的情况下计算二值图像中标记对象的边界像素数。 main() 函数将循环遍历二值图像 'mask' 中所有标记的对象,并计算每个对象的边界像素数。
我想知道在这个 Cython 代码中传递或 return 我的变量的最佳方法是什么。这些变量要么在 NumPy 数组中,要么在类型化的内存视图中。我弄乱了 passing/returning 不同格式的变量,但无法推断出 best/most 有效的方法是什么。我是 Cython 的新手,所以 Memoryviews 对我来说仍然相当抽象,这两种方法之间是否存在差异仍然是个谜。我正在处理的图像包含 100,000 多个标记对象,因此此类操作需要相当高效。
总结一下:
When/should 我 pass/return 我的变量作为类型化的 Memoryviews 而不是 NumPy 数组来进行非常重复的计算?有没有最好的方法或者它们完全相同?
%%cython --annotate
import numpy as np
import cython
cimport numpy as np
DTYPE = np.intp
ctypedef np.intp_t DTYPE_t
@cython.boundscheck(False)
@cython.wraparound(False)
def erode(DTYPE_t [:,:] img):
# Image dimensions
cdef int height, width, local_min
height = img.shape[0]
width = img.shape[1]
# Padded Array
padded_np = np.zeros((height+2, width+2), dtype = DTYPE)
cdef DTYPE_t[:,:] padded = padded_np
padded[1:height+1,1:width+1] = img
# Eroded image
eroded_np = np.zeros((height,width),dtype=DTYPE)
cdef DTYPE_t[:,:] eroded = eroded_np
cdef DTYPE_t i,j
for i in range(height):
for j in range(width):
local_min = min(padded[i+1,j+1], padded[i,j+1], padded[i+1,j],padded[i+1,j+2],padded[i+2,j+1])
eroded[i,j] = local_min
return eroded_np
@cython.boundscheck(False)
@cython.wraparound(False)
def border_image(slice_np):
# Memoryview of slice_np
cdef DTYPE_t [:,:] slice = slice_np
# Image dimensions
cdef Py_ssize_t ymax, xmax, y, x
ymax = slice.shape[0]
xmax = slice.shape[1]
# Erode image
eroded_image_np = erode(slice_np)
cdef DTYPE_t[:,:] eroded_image = eroded_image_np
# Border image
border_image_np = np.zeros((ymax,xmax),dtype=DTYPE)
cdef DTYPE_t[:,:] border_image = border_image_np
for y in range(ymax):
for x in range(xmax):
border_image[y,x] = slice[y,x]-eroded_image[y,x]
return border_image_np.sum()
@cython.boundscheck(False)
@cython.wraparound(False)
def main(DTYPE_t[:,:] mask, int numobjects, Py_ssize_t[:,:] indices):
# Memoryview of boundary pixels
boundary_pixels_np = np.zeros(numobjects,dtype=DTYPE)
cdef DTYPE_t[:] boundary_pixels = boundary_pixels_np
# Loop through each object
cdef Py_ssize_t y_from, y_to, x_from, x_to, i
cdef DTYPE_t[:,:] slice
for i in range(numobjects):
y_from = indices[i,0]
y_to = indices[i,1]
x_from = indices[i,2]
x_to = indices[i,3]
slice = mask[y_from:y_to, x_from:x_to]
boundary_pixels[i] = border_image(slice)
return boundary_pixels_np
Memoryviews 是 Cython 的最新补充,旨在改进原始 np.ndarray
语法。出于这个原因,他们略微偏爱。不过,您使用的通常不会有太大区别。这里有一些注意事项:
速度
就速度而言,它非常差别不大 - 我的经验是作为函数参数的内存视图稍微慢一些,但几乎不值得担心。
一般性
Memoryviews 被设计用于任何具有 Python 缓冲区接口的类型(例如标准库 array
模块)。输入 np.ndarray
仅适用于 numpy 数组。原则上,memorviews 可以支持偶数 wider range of memory layouts,这可以使与 C 代码的接口更容易(实际上我从来没有真正看到它有用)。
作为 return 值
当从 Cython return 将数组转换为代码 Python 时,用户可能会更喜欢 numpy 数组而不是 memoryview。如果您正在使用内存视图,您可以执行以下任一操作:
return np.asarray(mview)
return mview.base
易于编译
如果您正在使用 np.ndarray
,则必须在 setup.py
文件中使用 np.get_include()
设置包含目录。您不必使用内存视图执行此操作,这通常意味着您可以跳过 setup.py
并仅使用 cythonize
命令行命令或 pyximport
用于更简单的项目。
并行化
这就是 memoryviews 相对于 numpy 数组的大优势(如果你想使用它的话)。它不需要全局解释器锁来获取内存视图的切片,但它需要一个 numpy 数组。这意味着以下代码大纲可以与内存视图并行工作:
cdef void somefunc(double[:] x) nogil:
# implementation goes here
cdef double[:,:] 2d_array = np.array(...)
for i in prange(2d_array.shape[0]):
somefunc(2d_array[i,:])
如果您不使用 Cython 的并行功能,这不适用。
cdef
类
您可以将内存视图用作 cdef
类 的属性,但不能用作 np.ndarray
的属性。您(当然)可以使用 numpy 数组作为无类型的 object
属性。
我正在编写 Python 代码来加速二值图像中标记对象的区域属性函数。以下代码将在给定对象索引的情况下计算二值图像中标记对象的边界像素数。 main() 函数将循环遍历二值图像 'mask' 中所有标记的对象,并计算每个对象的边界像素数。
我想知道在这个 Cython 代码中传递或 return 我的变量的最佳方法是什么。这些变量要么在 NumPy 数组中,要么在类型化的内存视图中。我弄乱了 passing/returning 不同格式的变量,但无法推断出 best/most 有效的方法是什么。我是 Cython 的新手,所以 Memoryviews 对我来说仍然相当抽象,这两种方法之间是否存在差异仍然是个谜。我正在处理的图像包含 100,000 多个标记对象,因此此类操作需要相当高效。
总结一下:
When/should 我 pass/return 我的变量作为类型化的 Memoryviews 而不是 NumPy 数组来进行非常重复的计算?有没有最好的方法或者它们完全相同?
%%cython --annotate
import numpy as np
import cython
cimport numpy as np
DTYPE = np.intp
ctypedef np.intp_t DTYPE_t
@cython.boundscheck(False)
@cython.wraparound(False)
def erode(DTYPE_t [:,:] img):
# Image dimensions
cdef int height, width, local_min
height = img.shape[0]
width = img.shape[1]
# Padded Array
padded_np = np.zeros((height+2, width+2), dtype = DTYPE)
cdef DTYPE_t[:,:] padded = padded_np
padded[1:height+1,1:width+1] = img
# Eroded image
eroded_np = np.zeros((height,width),dtype=DTYPE)
cdef DTYPE_t[:,:] eroded = eroded_np
cdef DTYPE_t i,j
for i in range(height):
for j in range(width):
local_min = min(padded[i+1,j+1], padded[i,j+1], padded[i+1,j],padded[i+1,j+2],padded[i+2,j+1])
eroded[i,j] = local_min
return eroded_np
@cython.boundscheck(False)
@cython.wraparound(False)
def border_image(slice_np):
# Memoryview of slice_np
cdef DTYPE_t [:,:] slice = slice_np
# Image dimensions
cdef Py_ssize_t ymax, xmax, y, x
ymax = slice.shape[0]
xmax = slice.shape[1]
# Erode image
eroded_image_np = erode(slice_np)
cdef DTYPE_t[:,:] eroded_image = eroded_image_np
# Border image
border_image_np = np.zeros((ymax,xmax),dtype=DTYPE)
cdef DTYPE_t[:,:] border_image = border_image_np
for y in range(ymax):
for x in range(xmax):
border_image[y,x] = slice[y,x]-eroded_image[y,x]
return border_image_np.sum()
@cython.boundscheck(False)
@cython.wraparound(False)
def main(DTYPE_t[:,:] mask, int numobjects, Py_ssize_t[:,:] indices):
# Memoryview of boundary pixels
boundary_pixels_np = np.zeros(numobjects,dtype=DTYPE)
cdef DTYPE_t[:] boundary_pixels = boundary_pixels_np
# Loop through each object
cdef Py_ssize_t y_from, y_to, x_from, x_to, i
cdef DTYPE_t[:,:] slice
for i in range(numobjects):
y_from = indices[i,0]
y_to = indices[i,1]
x_from = indices[i,2]
x_to = indices[i,3]
slice = mask[y_from:y_to, x_from:x_to]
boundary_pixels[i] = border_image(slice)
return boundary_pixels_np
Memoryviews 是 Cython 的最新补充,旨在改进原始 np.ndarray
语法。出于这个原因,他们略微偏爱。不过,您使用的通常不会有太大区别。这里有一些注意事项:
速度
就速度而言,它非常差别不大 - 我的经验是作为函数参数的内存视图稍微慢一些,但几乎不值得担心。
一般性
Memoryviews 被设计用于任何具有 Python 缓冲区接口的类型(例如标准库 array
模块)。输入 np.ndarray
仅适用于 numpy 数组。原则上,memorviews 可以支持偶数 wider range of memory layouts,这可以使与 C 代码的接口更容易(实际上我从来没有真正看到它有用)。
作为 return 值
当从 Cython return 将数组转换为代码 Python 时,用户可能会更喜欢 numpy 数组而不是 memoryview。如果您正在使用内存视图,您可以执行以下任一操作:
return np.asarray(mview)
return mview.base
易于编译
如果您正在使用 np.ndarray
,则必须在 setup.py
文件中使用 np.get_include()
设置包含目录。您不必使用内存视图执行此操作,这通常意味着您可以跳过 setup.py
并仅使用 cythonize
命令行命令或 pyximport
用于更简单的项目。
并行化
这就是 memoryviews 相对于 numpy 数组的大优势(如果你想使用它的话)。它不需要全局解释器锁来获取内存视图的切片,但它需要一个 numpy 数组。这意味着以下代码大纲可以与内存视图并行工作:
cdef void somefunc(double[:] x) nogil:
# implementation goes here
cdef double[:,:] 2d_array = np.array(...)
for i in prange(2d_array.shape[0]):
somefunc(2d_array[i,:])
如果您不使用 Cython 的并行功能,这不适用。
cdef
类
您可以将内存视图用作 cdef
类 的属性,但不能用作 np.ndarray
的属性。您(当然)可以使用 numpy 数组作为无类型的 object
属性。