Python:CFFI結(jié)合Numpy使用

CFFI 是Python的C語言外部函數(shù)接口。通過CFFI,Python可以與C語言代碼進行交互,使用起來也比較方便。
本文主要內(nèi)容有:

  • CFFI數(shù)組的基本使用
  • CFFI數(shù)組和Numpy ndarry相互轉(zhuǎn)換

測試環(huán)境:Python3.7.4(x64) + CFFI 1.3.1 + VS2019

安裝CFFI

運行pip install cffi即可。
測試:

import cffi

print(cffi.__version__)
1.13.1

CFFI數(shù)組基本使用

首先導(dǎo)入庫文件并實例化FFI

import numpy as np
from cffi import FFI
ffi = FFI()

CFFI數(shù)組使用

ffi.new
ffi.new(cdecl, init=None)根據(jù)指定的C語言類型創(chuàng)建實例并返回指針,如果C語言類型是數(shù)組,則返回它的引用。具體介紹見ffi.new。
例如:

a = ffi.new("int[]", 10)
print(len(a), a, type(a))
10 <cdata 'int[]' owning 40 bytes> <class '_cffi_backend.CDataOwn'>

對于返回的<cdata>指針,可以直接通過下標對其讀取和賦值,也可以進行切片操作。使用切片時,注意不能省略開始和結(jié)束位置,不能使用負索引,不能使用步長。

# 通過下標讀取賦值
a[0] = 11
a[1] = 55
for i in range(len(a)):
    print(a[i], end=' ')
11 55 0 0 0 0 0 0 0 0 

為了簡便,后續(xù)<cdata>數(shù)組打印,直接將其轉(zhuǎn)換為list。

切片
<cdata>數(shù)組的切片是對原數(shù)組的引用,它們共用相同的內(nèi)存,對新切片進行賦值操作會影響原數(shù)組,這和Python的list切片操作有很大的不同。

# 切片賦值
a = ffi.new("int[]", 10)
a[3:5]=[11,22] # 長度必須一致
print(list(a))

# 切片引用
b = a[5:10]
print(b)
b[2] = 1200
print(list(a), list(b))
[0, 0, 0, 11, 22, 0, 0, 0, 0, 0]
<cdata 'int[]' sliced length 5>
[0, 0, 0, 11, 22, 0, 0, 1200, 0, 0] [0, 0, 1200, 0, 0]

從上面可以看出,數(shù)組a的切片也是<cdata>類型,對b的賦值操作會影響a數(shù)組。

多維數(shù)組
多維數(shù)組最簡單的定義方式是定義時直接指定數(shù)組大小,可以避免復(fù)雜的初始化。例如二維數(shù)組使用ffi.new("int[2][10]"),三維數(shù)組使用ffi.new("int[2][5][10]")。使用len獲取多維數(shù)組長度時,返回的是第一維的長度,例如:

b = ffi.new("int[2][5][10]")
print(b)
print(len(b), len(b[0]), len(b[0][0]))
<cdata 'int[2][5][10]' owning 400 bytes>
2 5 10

多維數(shù)組同樣是通過下標進行讀取賦值:

b[0][0][0] = 100
b[0][0][1:3] = [2,3]
print(list(b[0][0]))
[100, 2, 3, 0, 0, 0, 0, 0, 0, 0]

CFFI數(shù)組解包

ffi.unpack
ffi.unpack(cdata, length)解包指定長度的C語言數(shù)組,并返回一個list。對于一維數(shù)組,ffi.unpacklist()的效果類似。從測試結(jié)果來看,當數(shù)組長度較小時,list()的性能好一些;數(shù)據(jù)量變大時,ffi.unpack就更具優(yōu)勢,推薦編程時使用ffi.unpack對數(shù)組進行解包操作。

a = ffi.new("int[]", 10)
a[1] = 100
b = ffi.unpack(a, 10)
print(b, type(b))
print(b == list(a))
%timeit ffi.unpack(a, 10)
%timeit list(a)

a = ffi.new("int[]", 10000)
%timeit ffi.unpack(a, 10000)
%timeit list(a)
[0, 100, 0, 0, 0, 0, 0, 0, 0, 0] <class 'list'>
True
451 ns ± 36 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
265 ns ± 4.4 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
54.2 μs ± 2.05 μs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
74.8 μs ± 294 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)

對于多維數(shù)組,例如a = ffi.new("int[4][10]"),解包時需要指定一維數(shù)組長度,使用時注意長度不要越界。

from pprint import pprint

a = ffi.new("int[4][10]")
a[3][9] = 1000

b = ffi.unpack(a,5)
pprint(b)

# 解包a[3]
b3 =  ffi.unpack(b[3],10)
pprint(b3)

# 或者直接解包a[3]
b3 =  ffi.unpack(a[3],10)
pprint(b3)
[<cdata 'int[10]' 0x0000027A3AA28548>,
 <cdata 'int[10]' 0x0000027A3AA28570>,
 <cdata 'int[10]' 0x0000027A3AA28598>,
 <cdata 'int[10]' 0x0000027A3AA285C0>,
 <cdata 'int[10]' 0x0000027A3AA285E8>]
[0, 0, 0, 0, 0, 0, 0, 0, 0, 1000]
[0, 0, 0, 0, 0, 0, 0, 0, 0, 1000]

可以看到,ffi.unpack操作返回一個list,其元素是<cdata>數(shù)組。多維數(shù)組的解包不是很直觀,使用也不多,實際使用時,可以將其轉(zhuǎn)換為Numpy ndarry。

CFFI數(shù)組和Numpy 相互轉(zhuǎn)換

ndarray轉(zhuǎn)CFFI數(shù)組

CFFI提供了ffi.from_buffer([cdecl,] python_buffer, require_writable=False)函數(shù),可以把python_buffer轉(zhuǎn)換為<cdata>數(shù)組。其中cdeclpython_buffer類型,默認為<cdata 'char[]'>。require_writable如果為True,則緩沖區(qū)python_buffer必須可寫,否則函數(shù)會調(diào)用失敗,具體介紹見ffi.from_buffer。
ndarray轉(zhuǎn)CFFI數(shù)組:

a = np.arange(10, dtype=np.int)
# ndarray to cdata
b = ffi.from_buffer("int*", a)
print(b)
print(ffi.unpack(b, 10)) # 由于b是"int *"類型,不能使用list()

b[3] = 300
print(a, ffi.unpack(b, 10))
<cdata 'int *' buffer from 'numpy.ndarray' object>
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[  0   1   2 300   4   5   6   7   8   9] [0, 1, 2, 300, 4, 5, 6, 7, 8, 9]

把ndarray轉(zhuǎn)換為<cdata>數(shù)組時,它們共用同一塊內(nèi)存,對b的賦值操作也會影響數(shù)組a。
另外可以使用ffi.cast函數(shù),把ndarray轉(zhuǎn)換為<cdata>數(shù)組,例如:

b = ffi.cast("int*", a.ctypes.data)

其效果和ffi.from_buffer一樣。

CFFI數(shù)組轉(zhuǎn)ndarray

CFFI數(shù)組轉(zhuǎn)ndarray有兩步:

  1. 先用ffi.buffer(cdata, [size])把CFFI數(shù)組轉(zhuǎn)換成python buffer。需要注意size是字節(jié)長度。
  2. 再用numpy.frombuffer把python buffer轉(zhuǎn)換成ndarray。實際使用時,需要指定dtypecount。

例如:

a = np.arange(10, dtype=np.int)

# ndarray to cdata
b = ffi.from_buffer("int*", a)

# cdata to buffer
c = ffi.buffer(b, 40)

# buffer to ndarray
d = np.frombuffer(c, dtype=np.int, count=10)
print(d)
d[1] = 100
print(a, d)
[0 1 2 3 4 5 6 7 8 9]
[  0 100   2   3   4   5   6   7   8   9] [  0 100   2   3   4   5   6   7   8   9]

可以看到,ad對應(yīng)的是同一塊內(nèi)存。

下面使用float型完整介紹一遍CFFI數(shù)組和ndarray的相互轉(zhuǎn)換:

a = np.arange(10, dtype=np.float32)

# ndarray to cdata
b = ffi.from_buffer("float*", a)

# cdata to buffer
c = ffi.buffer(b, 40) # 10 * 4 = 40
# buffer to ndarray
d = np.frombuffer(c, dtype=np.float32, count=10) # 數(shù)據(jù)類型需要對應(yīng)
print(d)
d[1] = 100.0
print(a, d)
[0. 1. 2. 3. 4. 5. 6. 7. 8. 9.]
[  0. 100.   2.   3.   4.   5.   6.   7.   8.   9.] [  0. 100.   2.   3.   4.   5.   6.   7.   8.   9.]

以上就是本文的內(nèi)容,后續(xù)會介紹下cfficythonctypes性能對比。
本文代碼地址:https://github.com/txfly/cffi_cython/archive/master.zip

版權(quán)聲明:本文為「txfly」的原創(chuàng)文章,遵循 CC 4.0 BY-SA 版權(quán)協(xié)議,轉(zhuǎn)載請附上原文出處鏈接及本聲明。
原文鏈接:http://m.itdecent.cn/p/80e0423ee70e

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容