C桑(Cython)从入门到入土
2. np数组操作对比
Node sc518 Copy has image data of type unsigned long long
实际上, 👆🏻这个错误是我探索Cython
的缘起…
code1:
Form:
更现代的方法是使用内存视图而不是指针:
cdef np.uint32_t[:,:,::1] mv_buff = np.ascontiguousarray(im, dtype = np.uint32)
[:,;,::1] 语法告诉 Cython 内存视图在内存中是 3D 和 C 连续的。将类型定义为内存视图而不是 numpy 数组的优点是
它可以接受定义缓冲区接口的任何类型,例如内置数组模块或来自 PIL 成像库的对象。
Memoryviews 可以在不持有 GIL 的情况下传递,这对于并行代码很有用
要从内存视图获取指针,请获取第一个元素的地址:
cdef np.uint32_t* im_buff = &mv_buff[0,0,0]
这比 <np.uint32_t*>mv_buff.data 更好,因为它避免了强制转换,并且强制转换通常可以隐藏错误。
👇的内容是小度的回答:
这段代码涉及到Cython中的内存视图和numpy数组的使用。Cython是一种用来编写C扩展的Python语言,它可以提高Python代码的执行效率。让我们逐步解释这段代码:
-
cdef np.uint32_t[:,:,::1] mv_buff = np.ascontiguousarray(im, dtype = np.uint32)
:cdef np.uint32_t[:,:,::1] mv_buff
: 这定义了一个3D的内存视图mv_buff
,其数据类型为np.uint32_t
(无符号32位整数)。:
表示这个维度是开放的,也就是说它的大小可以根据源数组im
的大小来自动确定。np.ascontiguousarray(im, dtype = np.uint32)
: 这个函数将输入的numpy数组im
转换为连续的内存布局,并指定数据类型为np.uint32_t
。
-
[:,;,::1] 语法告诉 Cython 内存视图在内存中是 3D 和 C 连续的。
:[:,;,::1]
是Cython中的内存视图语法,它定义了数组的维度和连续性。这里它表示一个3D的数组,并且在内存中是C风格连续的(即行优先)。
-
将类型定义为内存视图而不是 numpy 数组的优点是:
- 可以接受定义缓冲区接口的任何类型,例如内置数组模块或来自 PIL 成像库的对象。
- Memoryviews 可以在不持有 GIL 的情况下传递,这对于并行代码很有用。
-
要从内存视图获取指针,请获取第一个元素的地址:
cdef np.uint32_t* im_buff = &mv_buff[0,0,0]
: 这行代码获取了内存视图mv_buff
中第一个元素的地址,并将其赋给指针im_buff
。这意味着现在可以通过这个指针直接访问和修改mv_buff
中的数据。
简而言之,这段代码的主要目的是将一个numpy数组转换为连续的内存布局(使用Cython的内存视图),并获取该内存视图的第一个元素的地址。这样做通常是为了提高数据访问的效率或与需要连续内存布局的C/C++代码进行交互。
code2
From
from cython.cimports.cython.view import array as cvarray
import numpy as np
# Memoryview on a NumPy array
narr = np.arange(27, dtype=np.dtype("i")).reshape((3, 3, 3))
narr_view = cython.declare(cython.int[:, :, :], narr)
# Memoryview on a C array
carr = cython.declare(cython.int[3][3][3])
carr_view = cython.declare(cython.int[:, :, :], carr)
# Memoryview on a Cython array
cyarr = cvarray(shape=(3, 3, 3), itemsize=cython.sizeof(cython.int), format="i")
cyarr_view = cython.declare(cython.int[:, :, :], cyarr)
# Show the sum of all the arrays before altering it
print(f"NumPy sum of the NumPy array before assignments: {narr.sum()}")
# We can copy the values from one memoryview into another using a single
# statement, by either indexing with ... or (NumPy-style) with a colon.
carr_view[...] = narr_view
cyarr_view[:] = narr_view
# NumPy-style syntax for assigning a single value to all elements.
narr_view[:, :, :] = 3
# Just to distinguish the arrays
carr_view[0, 0, 0] = 100
cyarr_view[0, 0, 0] = 1000
# Assigning into the memoryview on the NumPy array alters the latter
print(f"NumPy sum of NumPy array after assignments: {narr.sum()}")
# A function using a memoryview does not usually need the GIL
@cython.nogil
@cython.ccall
def sum3d(arr: cython.int[:, :, :]) -> cython.int:
i: cython.size_t
j: cython.size_t
k: cython.size_t
I: cython.size_t
J: cython.size_t
K: cython.size_t
total: cython.int = 0
I = arr.shape[0]
J = arr.shape[1]
K = arr.shape[2]
for i in range(I):
for j in range(J):
for k in range(K):
total += arr[i, j, k]
return total
# A function accepting a memoryview knows how to use a NumPy array,
# a C array, a Cython array...
print(f"Memoryview sum of NumPy array is {sum3d(narr)}")
print(f"Memoryview sum of C array is {sum3d(carr)}")
print(f"Memoryview sum of Cython array is {sum3d(cyarr)}")
# ... and of course, a memoryview.
print(f"Memoryview sum of C memoryview is {sum3d(carr_view)}")
# 此代码应提供以下输出:
NumPy sum of the NumPy array before assignments: 351
NumPy sum of NumPy array after assignments: 81
Memoryview sum of NumPy array is 81
Memoryview sum of C array is 451
Memoryview sum of Cython array is 1351
Memoryview sum of C memoryview is 451
👇的内容是小度的回答:
这段代码主要是关于Cython的内存视图(memoryview)特性,以及如何使用Cython与NumPy、C数组和Cython数组进行交互。下面是对代码的详细解释:
-
导入相关模块:
from cython.cimports.cython.view import array as cvarray
: 导入Cython的carray(C风格数组)作为cvarray。import numpy as np
: 导入NumPy库。
-
创建NumPy数组:
narr = np.arange(27, dtype=np.dtype("i")).reshape((3, 3, 3))
: 创建一个形状为(3,3,3)的整数数组,并初始化其值。
-
声明Cython内存视图:
narr_view = cython.declare(cython.int[:, :, :], narr)
: 为NumPy数组创建一个Cython内存视图。carr = cython.declare(cython.int[3][3][3])
: 声明一个C风格的3x3x3整数数组。cyarr = cvarray(shape=(3, 3, 3), itemsize=cython.sizeof(cython.int), format="i")
: 创建一个Cython的carray。
-
显示数组和赋值:
print(f"NumPy sum of the NumPy array before assignments: {narr.sum()}")
: 在赋值之前打印NumPy数组的总和。carr_view[...] = narr_view
和cyarr_view[:] = narr_view
: 将NumPy数组的值复制到C风格数组和Cython数组中。narr_view[:, :, :] = 3
: 将NumPy数组的所有元素设置为3。carr_view[0, 0, 0] = 100
和cyarr_view[0, 0, 0] = 1000
: 分别设置C风格数组和Cython数组的特定元素值。
-
检查赋值后的总和:
print(f"NumPy sum of NumPy array after assignments: {narr.sum()}")
: 在赋值之后打印NumPy数组的总和,由于所有元素都被设置为3,所以总和应为27。
-
定义一个使用内存视图的函数:
@cython.nogil
和@cython.ccall
是Cython的装饰器,它们允许函数在没有全局解释器锁(GIL)的情况下运行,并允许直接调用C函数。sum3d
函数接受一个三维整数数组作为参数,并返回其所有元素的总和。
-
调用内存视图函数:
使用先前定义的sum3d
函数,分别对NumPy数组、C风格数组、Cython数组以及C风格的内存视图调用该函数,并打印结果。
总的来说,这段代码展示了如何使用Cython的内存视图来与NumPy、C风格数组和Cython数组进行交互,以及如何通过内存视图进行高效的数值计算。
小结
C桑
这种啰嗦的语言风格, 真是让Python🐶抓狂😖…
不过, 这种高效倒是真的…
我们现在比纯 Python 版本快 7558 倍,比 NumPy 快 11.1 倍!
以上
未完待续